содержание   вперед   назад

20. Класс MyRW -- чтение и запись файлов

В какой-то момент начинаешь понимать, что копить один файл, в котором записывать все стандартные операции неоптимально, так как таких операций может быть много. И появляется желание сделать несколько таких файлов. Первым кандидатом, естественно, являются операции чтения и записи файлов, так как они бывают необходимы практически в каждой программе. Поэтому я создал файл с названием MyRW, в котором собраны все мои такие операции. Конечно, их может быть больше, но пока что есть. Как обычно, я буду ссылаться на номера строк в файле, который у вас уже есть, если вы прочитали предыдущую главу и скачали архив программы. Данная глава имеет вполне самостоятельный интерес, но она написана как дополнение к предыдущей главе, поэтому сначала посмотрите ее. Я буду двигаться по порядку.

readLine -- этот метод возвращает текст, записанный в n-ю строку файла. Иногда это удобно, если в файле записаны какие-либо настройки и структурированы по строкам. Это бывает также необходимо, если файл представляет собой базу данных, каждое сообщение в отдельной строке. Сама операция сделана таким образом, что не рассчитана на массовое использование. В ней прочитывается весь файл в текстовую строку методом fileToText данного класса. Затем в строке n-1 раз находится символ конца строки и берется остаток после этого символа. То есть фактически отрезается текст, содержащий n-1 строк. Затем снова находится символ конца строки. Если его нет, что возвращается вся строка, если есть, то часть текста от начала до этого символа. Возможно есть более оптимальные методы для такой работы, если в файле очень много строк. Но у меня все это нормально работает с файлами, в которых все записано в 10-15 строк.

writeLine -- этот метод делает более сложную работу. Он записывает в существующий файл n-ю строку. Ясно, что этот метод нужен для записи настроек в последовательные строки файла. Этот метод тоже считывает весь файл, но по другому. Он использует стандартный класс FileReader, в котором есть метод чтения файла по строкам. Строки считывают в цикле в массив строк lines, размер которого заранее задан как 10000. Но это ничего не значит, пока это только заявление о намерениях. При чтении файла элементы строк заполняются содержанием, а по окончании цикла (если новая строка оказалась пустая) в переменную im записывается полное число строк. Далее надо обработать варианты. Если указанный номер строки превышает полное число строк, то проверяется значение логической переменной MyPro.ok. Если оно равно false, то ничего не делается, в противном случае работаем дальше. А дальше обрабатывается переменная MyPro.rus. Стандартно она равна нулю и ничего делать не надо. При этом в процессе записи по тому коду, который написан, русские символы в новой строке, которую надо записать, автоматически переводятся в аски коды в Виндовс кодировке. Но мне приходилось работать за границей в других версиях системы Виндовс. И там это не делается. И необходимо обрабатывать русские символы специально, подбирая константу, которую надо вычесть для того, чтобы все было нормально.

А затем начинается второй этап записи файла по строкам. Для этого используется класс FileWriter, который вставляется в класс PrintWriter, а последний имеет стандартные методы записи как в языке С, то есть println записывает полную строку и ставит в конце признак конца строки, а print записывает текст, но строку не заканчивает. При этом сначала снова записываются n-1 строк. Но если число строк в файле меньше, то вместо тех строк, которых не хватает, записываются строки, состоящие из одного пробела. Затем проверяется переменная MyPro.ok и если она истинна, записывается новая срока. Если эта строка последняя в файле, то она не заканчивается, а если нет, то заканчивается. Ну и потом дописываются оставшиеся строки. Таким образом, в зависимости от значения логической переменной MyPro.ok новая строка либо будет записана, либо в файле просто исчезнет n-я строка. Иногда бывает полезно просто стереть в файле одну строку. Это делается в рамках одной процедуры. Данная процедура интересна с точки зрения применения указанных выше классов. Но часто я поступаю по другому, то есть считываю и записываю текстовые файлы как байтовый массив. Но об этом далее.

fileToText(File fs) -- этот метод мы уже использовали в программе. Как и следует из названия, этот метод копирует текст из файла в текстовую переменную программы. Работает это очень просто через класс DataInputStream, который читает файлы как данные. Сам файл передается в этот класс через буфер, что означает чтение файла через буфер, то есть не побайтно, а большими кусками сразу. И у этого класса используется только один метод readFully(ba,0,n) и еще один, чтобы закрыть поток close(). Этот метод считывает байты из файла в байтовый массив, начиная с первого индекса (который 0) и всего n штук. А число n определяется по размеру файла. Теперь байты надо переписать в юникоды массива юникодов char[n]. В процессе этой работы исключаются из массива коды с номером 13, а также отрицательные байты переводятся в юникоды русских символов добавлением магического числа 1104. Этот нехитрый прием позволяет не связываться с проблемами локализации и, в то же время, работать с русским текстом в Виндовс кодировке.

fileToText(URL u) -- этот метод мы тоже уже использовали в программе. У него такое же название, но другой аргумент. В Java это допускается и эти процедуры считаются разными. Так оно и есть. Здесь все сделано по другому. Во-первых здесь источником является класс InputStream, который получается методом openStream() класса URL. При этом также читаются байты, но размер заранее не известен. Поэтому заказывается изначально большой размер (500000) и делается попытка прочитать в весь массив. Но операция выдает число n реально прочитанных байтов. В остальном все должно быть так же, как и в предыдущей процедуре. Но здесь сделано более сложно. Дело в том, что предыдущая процедура ориентировалась на файлы в системах Linux (Java) и Windows. Но есть еще система Apple, в которой признаком конца строки является как раз код 13. И если его просто ликвидировать, то в системе Apple все строки сольются. Поэтому более универсально и правильно делать проверку, а есть ли за кодом 13 код 10. И если он есть, то код 13 нужно убрать. А если нет, то его надо заменить на 10. Код данной процедуры более универсален. Но я никогда не работал на Apple компьютерах, так что мне все равно. Хотя более правильно писать как здесь.

textToFile -- это обратный метод к предыдущим и его мы тоже уже использовали в программе. Он записывает строковую переменную в файл. При этом файл заново создается или переписывается, если он был. Здесь сначала текст переводится в символьный массив, определяется его размер. Затем символьный массив перезаписывается в байтовый массив, используя перевод русских юникодов в аски коды виндосовской кодировки. И байтовый массив записывается через буфер. Но здесь есть одна тонкость. Буфер записывается в файл только в том случае, когда он полный. Но при этом конец байтового массива может остаться в буфере и не записаться в файл. Чтобы этого избежать надо применять команду bout.flush(). Она заставляет программу очистить буфер и записать его в файл даже если он не полный. С признаком конца строки ничего не делается. То есть всегда будет записан код 10 как принято в Java. Для ваших программ никаких проблем не будет: как записали, так и считали. Проблемы будут при использовании других программ в других операционных системах. Однако сейчас многие редакторы текстов понимают все коды окончания строк и могут их поменять с любого на любой.

Теперь переходим к процедурам работы с zip-архивами. Первая процедура zipCat по имени файла возвращает строку, в которой записаны имена всех файлов, записанных в zip-архиве с данным именем. Причем имя файла zip-архива может быть любым. Так как мы не знаем заранее сколько будет имен, то нужно открывать объект класса StringBuffer. Про него я уже писал в предыдущих главах. Далее потоки из файла через буфер ведут в класс ZipInputStream. У этого класса есть метод getNextEntry(), который возвращает одну единицу записи в архиве, объект класса ZipEntry. А у последнего класса есть метод getName(), который возвращает имя файла в архиве. Это имя надо добавить в текстовый буфер и закончить символом разделителя. В качестве такого символа может быть любой, но я привык использовать вертикальную черту, она очень редко или совсем не используется в именах. Далее текстовый буфер надо преобразовать в строку и вернуть без последней вертикальной черты. Так удобнее при разделении на строки. Все очень просто.

Вторая процедура zipFiles позволяет создать zip-архив по имени файла архива и списку имен архивируемых файлов. Эти имена образуют текстовый массив. Здесь по имени файла образуется поток как объект класса ZipOutputStream. Далее в цикле по числу файлов создается объект класса File, затем проверяется его наличие, а также, что файл реально является файлом. Дело в том, что объектами этого класса могут быть как файлы, так и папки, и в некоторых списках папки тоже могут присутствовать. Вообще говоря, их тоже надо записывать в архив, но я это не умею, да и в этом нет необходимости, все и так работает. Далее по имени файла создается объект класса ZipEntry. И методом putNextEntry(ze) записывается заголовок нового файла в архиве. А затем надо честно прочитать файл в байтовый массив и честно записать байтовый массив в архив. Затем закрыть запись. Сжатие файла происходит автоматически, причем уровень сжатия не указывается, используется по умолчанию, хотя можно и указывать, но это более сложно и не имеет смысла. Все эти процедуры проделываются в цикле после чего поток закрывается.

Третья процедура unzipFiles является чуть более сложной. Она делает копирование всех файлов из архива на компьютер. Аргументами являются имя файла архива и целый параметр, указывающий что делать с файлами после окончания работы программы. Здесь тоже проводиться буферизованное чтение, но размер буфера указывается в явном виде, это некоторая особенность данной процедуры, просто показывающая разнообразие способов реализации целей. Первоначально все выглядит как в первой процедуре, нам надо читать архив в потоке объекта класса ZipInputStream zis. В цикле по записям в архиве сначала определяется имя записи. А дальше надо проанализировать имя записи. Если оно заканчивается на символ "/", то это имя папки. Надо проверить есть ли такая папка на компьютере. Если нет, то ее надо создать методом mkdir(). Но если параметр mod = 1, и папка действительно была создана, об этом говорит логическая переменная fe, то надо заказать режим удаления папки по выходе из программы. Это делает метод deleteOnExit(). И перейти в новой записи, то есть сразу в конец итерации цикла. Но в нашем архиве такой записи не будет, она иногда встречается в других архивах. У нас могут быть имена реальных файлов с указанием их вложения в папки, то есть типа folder/file. Поэтому надо проверить наличие символа "/" в имени файла и определить положение последнего символа. Затем выделить часть имени, которая указывает на папку и снова проверить наличие этой папки на компьютере. А если нет, то надо ее создать заранее, иначе файл не будет записан. Ну и снова заказать режим удаления если необходимо. Но на этот раз на этом работа не заканчивается. Теперь надо реально скопировать файл. Все делается точно так же, как и выше, только в обратную сторону и с использованием заданного размера буфера. И точно также надо использовать метод flush(), чтобы очистить полупустой буфер. И снова предусмотреть удаление файла. если необходимо.

Другие процедуры мы в данной программе не использовали. Четвертая процедура unzipFile копирует из архива только один файл по его имени. Все делается точно так же, только размер буфера другой и имя папки определяется по другому. Причем это делается до основного цикла по имени заданного файла. А в основном цикле делается проверка на совпадение имени записи с именем файла. Копирование происходит только при совпадении. Тут есть одно дополнительное значение параметра mod. Если его значение равно 2 и файл уже существует на компьютере, то он не копируется. В противном случае файл перезаписывается. Иногда это полезно использовать в режиме инсталяции программы, когда необходимо скопировать из архива файлы только при первом запуске. А затем эти файлы перезаписываются самой программой и их нельзя изменять. Пятая процедура zipFolders записывает zip-архив целыми папками, по списку папок. Это бывает удобно в том смысле, что не надо формировать большой массив имен файлов. Достаточно все файлы записать в одну или несколько папок и записать сразу папки. Особенностью этой процедуры является то, что список имен файлов в указанной папке определяется в самой процедуре. Поэтому используется двойной цикл: по именам папок и по именам файлов внутри каждой папки. А сложные имена файлов тоже формируются внутри процедуры. Остальное аналогично. Шестая процедура unzipFolder копирует из архива одну папку на компьютер при тех же условиях, что и четвертая процедура. То есть если папка существует и mod = 2, то она не копируется. Остальное все аналогично. В цикле по всем записям из имени каждой записи выделяется имя папки и делается сравнение. При совпадении файл копируется.

Данный класс может быть в дальнейшем дополнен другими процедурами работы с файлами. Есть возможность считывать файлы из интернета, можно попробовать напрямую переписывать файлы из одного архива в другой архив, можно записывать в файл юникоды или utf-8 кодировку. Можно считывать файлы с разными фильтрами, можно упорядочивать слова в файлах, складывать слова в двух и более файлах и много других процедур. В принципе такие процедуры не всегда используются, но всегда удобно выделить их в отдельный класс. Но пока я описал самое главное.

содержание   вперед   назад


.