[Назад]
[Вперед]
Виктор Кон, vkBook, Java-Second
Изучаем класс MyPro
Вы уже знаете, что класс MyPro -- это просто библиотека общих переменных и объектов,
а также общих функций. Правильнее говорить библиотека полей и методов, доступных для
использования в любом классе, так как все они имеют атрибут public static. Чтобы
посмотреть полный код класса в отдельном окне кликните эту ссылку.
В этом тексте я не буду повторять весь код, а просто прокомментирую, что написано в
полном тексте. Про строки import мы говорили в первом цикле и тут все ясно. Далее, после
объявления класса идет список всех его полей. Такая структура совсем не обязательна, поля
можно объявлять где угодно. Но для удобства чтения лучше себя ограничивать и соблюдать
определенный порядок, который был принят в старых языках. А именно, все поля объявляются в
самом начале класса. Практика показывает, что разумное ограничение свободы ведет к лучшему
качеству жизни. Программирование не является исключением из этого правила. Все указанные
здесь поля (переменные) используются в разных местах программы и я буду объяснять их
назначение по ходу их использования. Здесь же я поясню лишь наличие длинного списка объектов
класса ImageIcon. Зачем это надо. Мы предполагаем использовать программу Менеджера файлов.
При показе файлов в каталоге в специальном окне часто имена файлов, которые используются в
конкретной программе, сопровождаются иконкой этой программы, чтобы наглядно представить
какая программа эти файлы использует. Версия 1.4.2 для таких файлов даже не пишет расширение,
что вообще говоря неудобно и без иконки вообще трудно понять какой это файл. С другой стороны,
так как мы не используем родную операционную систему, то все иконки мы должны создавать себе
сами. Они как раз и являются объектами класса ImageIcon и, так как они используются в разных
местах, удобно их все объявить и определить в общем месте. Здесь объявлены иконки для наиболее
типичных расширений файлов, но этот список можно и увеличить, если необходимо.
Перейдем к описанию общих методов. Первый метод init определяет все иконки. Вообще говоря, мы
могли бы определить все иконки сразу же при объявлении полей. Но в нашем случае для определения
иконок мы используем файлы из папки data относительно текущей папки программы, и изначально эти
файлы существовать не будут, так как мы их будем создавать в процессе работы программы, вынимая
из архива. Поэтому единственно правильным решением является определение объектов класса в
специальном методе, который будет вызван после того, как указанные файлы появятся в нужной
папке. Для каждого расширения заготовлена отдельная иконка в виде картинки, на которой это
расширение и написано. Но это не обязательно, можно и другие картинки приготовить. Интересно,
что в Java, в отличие от Windows, картинки иконок могут находиться в любом формате, не обязательно
ico. Для версии 1.4.2 я использую PNG. Но в версии 1.2.2 PNG формат не поддерживается и
приходится использовать GIF.
Следующий метод readLine был описан в цикле First. Но здесь к нему добавлен и зеркальный
метод writeLine. Если считывание строки с заданным номером из файла является относительно
простой процедурой, то запись строки с заданным номером в файл предполагает много вариантов.
Первый - в файле может быть меньше строк. В этом случае выполняются следующие действия:
добавляется нужное количество строк из одного пробела и затем записывается строка с заданным
номером. Второе - строка с заданным номером уже есть. В этом случае она заменяется на новое
содержание. Полезное дополнение состоит в том, что если строка с заданным номером есть, то
она просто вычеркивается из файла и число строк уменьшается на единицу. Алгоритм работы
следующий. Определяется пустая строка eline, затем большой массив строк lines размером
10000. Это некоторое ограничение на размер обрабатываемого файла, но не слишком серьезное.
С другой стороны, о размерах памяти можно не беспокоиться, так как реальных строк у нас все
равно пока нет. Затем по имени файла создается объект класса File. Если файл существует,
(метод exists()) то все его строки считываются в массив строк и переменная im равна полному
числу строк в файле. Далее используется логическая переменная ok. Обратите внимание, она
не передается через параметры и нигде не определяется. Но ошибки нет. Все дело в том, что
эта переменная описана как public. И значит мы можем определить ее в любом другом классе
перед вызовом метода writeLine. Это есть второй альтернативный способ передачи параметров
в подпрограмму. Переменная ok как раз и реализует дополнение, при котором строка в файл
не записывается. На втором прогоне мы снова записываем все строки в файл, используя объект
класса PrintWriter и его методы print и println. Первый записывает строку не закрывая
ее (то есть без записи признака конца строки), второй ее закрывает. Это точно соответсвует
многим языкам и является C-подобным синтаксисом. Разделение необходимо, если наша строка
записывается в файл последней. Обработка на возможную ошибку вам уже знакома.
Следующие методы split и parent были описаны в цикле First. Метод ttiarr написан
самостоятельно, хотя, в принципе, в Java есть для этого готовые методы. Он имеет два аргумента:
строку и число и возвращает массив целых чисел. Его назначение -- прочитать строку и
вернуть ровно n целых чисел, которые в этой строке могут находиться, а могут и не находиться.
То есть там может быть или меньше или больше чисел. Как вы помните для одного числа есть метод
Integer.parseInt(text) и в сочетании с методом nextToken() класса StringTokenizer все можно
было бы сделать иначе. Но интересно поучиться как это можно сделать на примитивном уровне.
Нам нужен массив точно из n чисел. Мы сначала его объявляем, затем сразу всем его элементам
присваиваем значение 999999. И только затем начинаем читать строку. Метод substring(k,m) выделяет
из строки ее часть начиная с индекса k и кончая индексом m-1. Разница m-k точно равно числу
выделяемых символов. Сначала мы проверяем каждый символ на пробел, двигая индекс m. Если вся
строка пустая, то возвращаем заготовленный массив. Если нет - берем число как индекс
специальной строки. Метод indexOf(text) находит первый индекс строки, как положение указанного
текста в этой строке. У нас текст состоит из одного символа и его индекс как раз даст число,
которое этот символ обозначает. Если символ не является числом, то метод вернет -1. Делаем
проверку на число и если все ОК, записываем его в соотвествующий разряд полного числа iv.
Как только проверка не сработает число закрываем и записываем. Опять прогоняем пробелы и так далее.
Внутри цикла while( i*(9-i) >= 0 ) стоит проверка на окончание строки и оператор break
прерывает цикл принудительно, если проверка даст положительный результат.
Следующий достаточно простой, но полезный метод getExt(File f) просто возвращает расширение
имени файла переведенное в нижний регистр (строчные буквы). Здесь сначала используется метод
getName() класса File, затем метод lastIndexOf(text), который, в отличие от указанного выше
метода indexOf(text), тоже возвращает индекс, но сканируя строку от конца. Опять делается
проверка на правильность индекса и если она проходит, то возвращается расширение, а если нет,
то возвращается null.
Последние 5 методов реализуют работу с zip-архивами. Мы реально будем использовать только
один из этих методов. Но я решил привести все 5 сразу, так как они в целом образуют некий комплекс
методов, позволяющий эффективно работать с zip-файлами в будущем. Сразу замечу, что для работы
этих методов имя файла не имеет никакого значения. Файл может иметь любое расширение, не обязательно
zip. Первый метод zipcat имеет аргументом имя файла и возвращает строку. Имя может быть как
полное, так и относительно папки программы. В возвращаемой строке записан каталог всех файлов,
которые находятся в данном zip-архиве, причем в качестве разделителя используется символ "|".
Как это работает. Я буду опускать очевидные детали и обсуждать только новые элементы, которые
еще не встречались в тексте. Таким новым элементом является класс StringBuffer. Он удобен тогда,
когда необходимо формировать строку из многих элементов, которые сами изначально неизвестны. У
него есть много методов из которых нам важны всего два: append(text) и toString(). Второй
метод есть у всех классов, так как он определен для прародителя - класса Object, и все прочие
классы, являясь его наследниками, получают метод по наследству. Сначала мы определяем цепочку
вложенных потоков. Байты считываются из файла в буфер, а затем передаются в объект класса
ZipInputStream. Метод getNextEntry() возвращает новый объект класса ZipEntry, который
содержит либо один заархивированный файл, либо заголовок папки, который также является элементом zip-структуры.
Все это делается в цикле до тех пор пока в файле есть новые объекты. У полученного объекта
берется имя и записывается в текстовый буфер вместе с разделителем. Затем текстовый буфер
преобразуется в строку и возвращается часть этой строки без последнего разделителя. Если
выполнить эту программу, то вы увидите, что имена простых файлов возвращаются обычным образом,
имена файлов внутри папок возвращаются в виде "папка/файл", а имена самих папок возвращаются
в виде "папка/". То есть возвращаются полные пути к файлу относительно начала zip-архива.
Следующий метод zipfold позволяет создать новый zip-файл, в который будут записаны несколько
папок целиком. Он имеет два аргумента: fnz - имя создаваемого архива и массив имен папок fold.
Здесь выходной поток zout напрямую связан с файлом с именем fnz. В момент, когда поток закрывается
файл записывается. Причем он записывается с самого начала и заменяет старый файл с тем же именем,
если таковой существует. Работает это так. Определяется размер массива имен, затем в цикле по
каждому имени объявляется объект класса File. Класс File описывает и папки и файлы, это создает
некоторую многозначность. Для ее разрешения существует логический метод isDirectory() возвращающий
true если объект является папкой. Проверяем это условие, и если все ОК, определяем массив
файлов внутри данной папки методом list(). Я уже говорил, что любой массив является объектом,
и как объект имеет поля. Поле length любого массива равно его размерности. Затем в цикле
определяем текущий файл по его составному имени. Проверка на наличие файла вообще говоря лишняя
и необходима только если вдруг из-за каких-то сбоев в каталоге есть имя несуществующего файла.
Затем точно так же объявляем часть zip-архива, соответствующую данному файлу и вставляем ее в поток
zout. Но это только объявление о намерениях, точнее шапка записи. Затем методом m=(int)fc.length()
определяем размер
файла. Здесь есть тонкость. Метод возвращает переменную типа long. Для того, чтобы приравнять ее
переменной типа int необходимо преобразовать long в int на что и указывает конструкция (int) после
знака равенства. Так надо делать всегда в процессах с понижением точности, иначе компилятор
зафиксирует ошибку. В операциях типа long = int это делать не нужно. Далее объявляем массив
байтов, поток чтения из файла через буфер и методом din.readFully(by,0,m) считываем весь файл.
Мы могли бы индексы не указывать, так как у нас указано от начала до конца. Но в таком виде можно
читать и не с начала и заданное число байтов. Второй индекс указывает число считываемых байтов
в отличие от метода substring. После того, как байты считаны из файла, их надо записать в zip
файл и закрыть запись. А после выполнения всех циклов - закрыть и сам zip-поток. Этот метод удобен
в том случае когда надо записать очень много файлов. Достаточно их все поместить в несколько папок
и записать эти папки.
Следующий метод мы как раз и будем использовать. Он позволяет вытащить из zip-архива одну
папку с заданным именем. Но среди его аргументов, помимо имени zip-архива и имени папки есть еще
целый параметр mod. Он может принимать 3 значения 0, 1 и 2. Значение 0 стандартное. Значение 1
означает, что вынутая папка и все ее файлы будут автоматически уничтожены после окончания работы
программы. Значение 2 означает, что если папка с данным именем уже существует, то ничего не делается.
В стандартном режиме в эту папку добавляются новые файлы в режиме замены.
Итак, если вам нужно вынуть папку с конфиденциальной информацией
только для работы программы - используете 1. Если же вам нужно провести инсталяцию программы и
вынуть папки просто для последующей работы - используете 2. Тогда в следующий раз запуск программы
будет проходить быстрее. Если вместо имени папки набрано "null", то это означает текущую папку.
Начнем разбираться в методе. Если в прошлый раз байты из файлов считывались
через буфер стандартного размера, то здесь заказывается размер буфера в переменную BUFFER.
Две логические переменные ok и fe позволяют упростить разбор вариантов. При этом fe равно true
когда имя папки не "null", а ok равно true когда нужная папка существует или только что создана.
Последнее делается методом ok=dir.mkdir(), который пытается создать папку и возвращает true если операция
прошла успешно. Как это работает. Открывается поток zis для чтения из zip-файла и подряд читаются
все записи. У каждой записи берется ее имя и сканируется с конца на поиск символа "/". Если символ
найден последним, то это имя папки, а не файла и запись игнорируется. Если все условия выполняются,
то есть имя папки существует в имени записи и совпадает с нужным или имя папки "null", а в имени
записи нет папки, начинается работа. Она состоит в том, что из zip-файла порциями считываются
байты, которые тут же записываются в файл с именем, равным имени записи. Из zip-файла считывается
только одна запись (затем надо использовать метод getNextEntry()) и при чтении выдается значение
считанных байтов или -1. Метод deleteOnExit() класса File как раз указывает, что данный файл
будет уничтожен при выходе из программы.
Как следует из рассмотренного алгоритма, прежде чем вынимать файлы в несуществующую папку, ее
надо создать. При этом если внутри папки существуют другие папки, то они не будут вынуты, так
как они расcматриваются как независимые папки с более сложным именем (с разделителем "/").
И в этом случае задача решается в несколько этапов. Например, вам надо вынуть папку с именем
"sect", внутри которой есть файлы и папка "subsect", внутри которой есть другие файлы. При
этом надо сначала применить метод к папке "sect". Будет создана данная папка и вынуты
файлы из этой папки, но без папки "subsect". А потом надо отдельно вынуть папку "sect/subsect".
Сначала эта папка будет создана и затем вынуты ее файлы. И так далее в более сложных
ситуациях. Можно было бы написать более сложную программу, в которой данный случай учитывался бы
автоматически. Но лично мне и так хорошо, а вам и карты в руки.
Остальные два метода zipfile и unzipfile делают аналогичную работу. Первый метод создает zip
файл в который помещает набор файлов по списку. Имена файлов могут быть указаны вместе с указателем
папки, в которой они находятся. Тогда в zip-файле автоматически формируются нужные папки. Этот
метод позволяет сформировать zip-файл произвольной конфигурации, но при этом для сложных архивов
необходимо сформировать длинный массив имен файлов. Второй метод позволяет вытащить из архива
один файл при тех же условиях, которые были рассмотрены для папки. И опять, если файл принадлежит
папке, которая не существует, то она создается. При вынимании многих файлов нужно соблюдать
очередь сверху вниз.
Мы изучили как работают методы класса MyPro. Надеюсь у вас понемногу появляется опыт для написания
собственных методов. Что касается этого класса, то вы можете смело ставить его в вашу программу
без всяких изменений. Причем можете ставить даже не Java код, а сразу откомпилированный класс
и спокойно использовать его методы в ваших классах. В этом сильная сторона объектных языков.
Можно использовать большое число откомпилированных классов вместо библиотек стандартных процедур
и просто прибавлять или убирать их из вашей программы. А сама виртуальная машина Java содержит
большой набор таких классов, которые вообще не надо прибавлять -- достаточно один раз установить
JVM. Интересно, что уже существующие классы от версии к версии улучшают свою дееспособность.
Если прокомпилировать программу с версией 1.2.2 и затем использовать ее с JVM версии 1.4.2
вы получите совсем другую, более продвинутую, программу при том же самом исходном коде. Вот почему
Java -- это не просто язык программирования, а более сложная технология.
[Назад]
[Вперед]