Модули для работы со строками байтов содержат большое количество функций, аналогичных функциям в модуле Data.List
, включая следующие (но не ограничиваясь ими): head
, tail
, init
, null
, length
, map
, reverse
, foldl
, foldr
, concat
, takeWhile
, filter
и др.
Есть и функции, имя которых совпадает с именем функций из модуля System.IO
, и работают они аналогично, только строки заменены значениями типа ByteString
. Например, функция readFile
в модуле System.IO
имеет тип
readFile :: FilePath –> IO String
а функция readFile
из модулей для строк байтов имеет тип
readFile :: FilePath –> IO ByteString
ПРИМЕЧАНИЕ. Обратите внимание, что если вы используете строгие строки и выполняете чтение файла, он будет считан в память целиком! При использовании ленивых байтовых строк файл будет читаться аккуратными порциями.
Копирование файлов при помощи Bytestring
Давайте напишем простую программу, которая принимает два имени файла в командной строке и копирует первый файл во второй. Обратите внимание, что модуль System.Directory
уже содержит функцию copyFile
, но мы собираемся создать нашу собственную реализацию.
import System.Environment
import qualified Data.ByteString.Lazy as B
main = do
(fileName1:fileName2:_) <– getArgs
copy fileName1 fileName2
copy :: FilePath –> FilePath –> IO ()
copy source dest = do
contents <– B.readFile source
bracketOnError
(openTemplFile "." "temp")
(\(tempName, tempHandle) -> do
hClose templHandle
removeFile tempName)
(\(tempName, tempHandle) -> do
B.hPutStr tempHandle contents
hClose tempHandle
renameFile tempName dest)
В функции main
мы получаем аргументы командной строки и вызываем функцию copy
, в которой всё волшебство и происходит. Вообще говоря, можно было бы просто прочитать содержимое одного файла и записать его в другой. Однако если бы что-то пошло не так (например, закончилось бы место на диске), у нас в каталоге остался бы файл с некорректным содержимым. Поэтому мы пишем во временный файл, который в случае возникновения ошибки просто удаляется.
Сначала для чтения содержимого входного файла мы используем функцию B.readFile
. Затем с помощью bracketOnError
организуем обработку ошибок. Мы получаем ресурс посредством вызова openTemplFile "." "temp"
, который возвращает пару из имени временного файла и его дескриптора. После этого указываем, что должно произойти при возникновении исключения. В этом случае мы закроем дескриптор и удалим временный файл. Наконец, выполняется собственно копирование. Для записи содержимого во временный файл используется функция B.hPutStr
. Временный файл закрывается, и ему даётся имя, которое он должен иметь в итоге.
Заметьте, что мы использовали B.readFile
и B.hPutStr
вместо их обычных версий. Для открытия, закрытия и переименования файлов специальные функции не требуются. Они нужны только для чтения и записи.
Проверим программу:
$ ./bytestringcopy bart.txt bort.txt
Обратите внимание, что программа, не использующая строки байтов, могла бы выглядеть точно так же. Единственное отличие – то, что мы используем B.readFile
и B.hPutStr
вместо readFile
и hPutStr
. Во многих случаях вы можете «переориентировать» программу, использующую обычные строки, на использование строк байтов, просто импортировав нужные модули и проставив имя модуля перед некоторыми функциями. В ряде случаев вам придётся конвертировать свои собственные функции для использования строк байтов, но это несложно.
Если вы хотите улучшить производительность программы, которая считывает много данных в строки, попробуйте использовать строки байтов; скорее всего, вы добьётесь значительного улучшения производительности, затратив совсем немного усилий. Обычно я пишу программы, используя обычные строки, а затем переделываю их на использование строк байтов, если производительность меня не устраивает.
Исключения[11]