Java: работа с файлами::Журнал СА 7.2003
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Подписка
Архив номеров
Где купить
Наука и технологии
Авторам
Рекламодателям
Контакты
   

  Опросы
  Статьи

Событие  

В банке рассола ждет сисадмина с полей фрактал-кукумбер

Читайте впечатления о слете ДСА 2024, рассказанные волонтером и участником слета

 Читать далее...

Организация бесперебойной работы  

Бесперебойная работа ИТ-инфраструктуры в режиме 24/7 Как обеспечить ее в нынешних условиях?

Год назад ИТ-компания «Крок» провела исследование «Ключевые тренды сервисного рынка 2023». Результаты

 Читать далее...

Книжная полка  

Читайте и познавайте мир технологий!

Издательство «БХВ» продолжает радовать выпуском интересных и полезных, к тому же прекрасно

 Читать далее...

СУБД PostgreSQL  

СУБД Postgres Pro

Сертификация по новым требованиям ФСТЭК и роль администратора без доступа к данным

 Читать далее...

Критическая инфраструктура  

КИИ для оператора связи. Готовы ли компании к повышению уровня кибербезопасности?

Похоже, что провайдеры и операторы связи начали забывать о требованиях законодательства

 Читать далее...

Архитектура ПО  

Архитектурные метрики. Качество архитектуры и способность системы к эволюционированию

Обычно соответствие программного продукта требованиям мы проверяем через скоуп вполне себе понятных

 Читать далее...

Как хорошо вы это знаете  

Что вам известно о разработках компании ARinteg?

Компания ARinteg (ООО «АРинтег») – системный интегратор на российском рынке ИБ –

 Читать далее...

Графические редакторы  

Рисование абстрактных гор в стиле Paper Cut

Векторный графический редактор Inkscape – яркий представитель той прослойки open source, с

 Читать далее...

День сисадмина  

Учите матчасть! Или как стать системным администратором

Лето – время не только отпусков, но и хорошая возможность определиться с профессией

 Читать далее...

День сисадмина  

Живой айтишник – это всегда движение. Остановка смерти подобна

Наши авторы рассказывают о своем опыте и дают советы начинающим системным администраторам.

 Читать далее...

Виртуализация  

Рынок решений для виртуализации

По данным «Обзора российского рынка инфраструктурного ПО и перспектив его развития», сделанного

 Читать далее...

Книжная полка  

Как стать креативным и востребованным

Издательский дом «Питер» предлагает новинки компьютерной литературы, а также книги по бизнесу

 Читать далее...

Книжная полка  

От создания сайтов до разработки и реализации API

В издательстве «БХВ» недавно вышли книги, которые будут интересны системным администраторам, создателям

 Читать далее...

Разбор полетов  

Ошибок опыт трудный

Как часто мы легко повторяем, что не надо бояться совершать ошибки, мол,

 Читать далее...

1001 и 1 книга  
19.03.2018г.
Просмотров: 6144
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 6857
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

12.03.2018г.
Просмотров: 4140
Комментарии: 0
Глубокое обучение с точки зрения практика

 Читать далее...

12.03.2018г.
Просмотров: 2978
Комментарии: 0
Изучаем pandas

 Читать далее...

12.03.2018г.
Просмотров: 3781
Комментарии: 0
Программирование на языке Rust (Цветное издание)

 Читать далее...

19.12.2017г.
Просмотров: 3789
Комментарии: 0
Глубокое обучение

 Читать далее...

19.12.2017г.
Просмотров: 6283
Комментарии: 0
Анализ социальных медиа на Python

 Читать далее...

19.12.2017г.
Просмотров: 3134
Комментарии: 0
Основы блокчейна

 Читать далее...

19.12.2017г.
Просмотров: 3434
Комментарии: 0
Java 9. Полный обзор нововведений

 Читать далее...

16.02.2017г.
Просмотров: 7246
Комментарии: 0
Опоздавших не бывает, или книга о стеке

 Читать далее...

17.05.2016г.
Просмотров: 10616
Комментарии: 0
Теория вычислений для программистов

 Читать далее...

30.03.2015г.
Просмотров: 12336
Комментарии: 0
От математики к обобщенному программированию

 Читать далее...

18.02.2014г.
Просмотров: 13969
Комментарии: 0
Рецензия на книгу «Читаем Тьюринга»

 Читать далее...

13.02.2014г.
Просмотров: 9100
Комментарии: 0
Читайте, размышляйте, действуйте

 Читать далее...

12.02.2014г.
Просмотров: 7053
Комментарии: 0
Рисуем наши мысли

 Читать далее...

10.02.2014г.
Просмотров: 5362
Комментарии: 3
Страна в цифрах

 Читать далее...

18.12.2013г.
Просмотров: 4594
Комментарии: 0
Большие данные меняют нашу жизнь

 Читать далее...

18.12.2013г.
Просмотров: 3402
Комментарии: 0
Компьютерные технологии – корень зла для точки роста

 Читать далее...

04.12.2013г.
Просмотров: 3129
Комментарии: 0
Паутина в облаках

 Читать далее...

03.12.2013г.
Просмотров: 3379
Комментарии: 0
Рецензия на книгу «MongoDB в действии»

 Читать далее...

02.12.2013г.
Просмотров: 3000
Комментарии: 0
Не думай о минутах свысока

 Читать далее...

Друзья сайта  

 Java: работа с файлами

Архив номеров / 2003 / Выпуск №7 (8) / Java: работа с файлами

Рубрика: Программирование /  Анализ данных

ДАНИИЛ АЛИЕВСКИЙ

Java: работа с файлами

Средства для работы с файлами – одно из немногих слабых мест системы библиотек языка Java. Файловые библиотеки появились в самых первых версиях Java и были ориентированы на операционные системы того времени, главным образом на UNIX. С тех пор операционные системы ушли далеко вперед, а области применения Java чрезвычайно расширились. Например, для текстовых файлов стали популярны форматы Unicode (16-битовые, UTF-8). Файловая иерархия в мире Windows резко усложнилась – появились такие понятия, как «Desktop», «My Computer», «My Documents». По соображениям совместимости фирма Sun была вынуждена сохранять старые классы и интерфейсы, дополняя их новыми. Получилась довольно запутанная система. В итоге правильная организация работы с файлами в современной Java-программе может оказаться достаточно непростой задачей.

В этой статье мы попытаемся рассмотреть наиболее типичные задачи, возникающие при работе с файлами, и предложить по возможности правильные способы их решения. Все приведенные решения являются фрагментами готовых отлаженных библиотек.

Чтение и запись бинарного файла

Самая простая задача – прочитать некоторый файл в виде массива байт, и наоборот, записать массив байт обратно в файл.

Это одна из немногих задач, решаемых стандартными библиотеками вполне очевидным способом. Основную трудность здесь представляет корректная обработка исключений. К сожалению, в большинстве учебников по Java предлагаются не вполне корректные решения, чреватые в случае ошибки «утечкой» ресурсов системы (файл остается незакрытым) или потерей информации об исключении.

Вот пример возможного решения.

Чтение файла:

  public static byte[] loadFileAsBytes(

    String fileName) throws IOException

  {

    return loadFileAsBytes(new File(fileName));

  }

  public static byte[] loadFileAsBytes(

    File file) throws IOException

  {

    byte[] result= new byte[(int)file.length()];

    loadFileAsBytes(file,result);

    return result;

  }

  public static void loadFileAsBytes(File file,

    byte[] buf) throws IOException

  {

    loadFileAsBytes(file,buf,0,buf.length);

  }

  public static void loadFileAsBytes(

     File file, byte[] buf, int off, int len)

     throws IOException

  {

    FileInputStream f= new FileInputStream(file);

    try {

      f.read(buf,off,len);

    } finally {

      try {f.close();} catch (Exception e) {};

    }

  }

Используется вариант FileInputStream байтового потока InputStream.

Обратите внимание: если файл удалось открыть (конструктор FileInputStream успешно сработал), то мы закрываем файл в любом случае, даже если процесс чтения оборвался из-за исключения. Однако исключения в процессе закрытия файла мы игнорируем. Действительно, если предыдущее чтение произошло безошибочно, т.е. мы успешно прочитали весь файл, то на невозможность закрыть файл можно не обращать внимания (тем более, что ошибка на этом этапе чрезвычайно маловероятна). Если же исключение произошло в процессе чтения, то, очевидно, оно представляет больший интерес, чем ошибка при попытке закрыть файл. Значит, «наружу» следует выдать именно исключение, связанное с чтением. Если бы мы не проигнорировали исключения при закрытии файла, то мы, в соответствии с известным неудобным свойством Java, рисковали бы «потерять» исключение при чтении файла в методе f.read().

Обращение к методу length() во втором варианте функции loadFileAsBytes достаточно «безопасно». Этот метод не порождает исключений, а при отсутствии файла или других возможных проблемах просто возвращает 0, что в данном контексте не приведет ни к чему плохому.

Запись файла:

  public static void saveFileFromBytes(

    String fileName, byte[] buf)

    throws IOException

  {

    saveFileFromBytes(new File(fileName),buf);

  }

  public static void saveFileFromBytes(

    File file, byte[] buf)

    throws IOException

  {

    saveFileFromBytes(file,buf,0,buf.length);

  }

  public static void saveFileFromBytes(

    File file, byte[] buf, int off, int len)

    throws IOException

  {

    FileOutputStream f= new FileOutputStream(file);

    try {

      f.write(buf,off,len);

    } catch (IOException e) {

      try {f.close();} catch (Exception e1) {};

      return;

    }

    f.close();

  }

Используется вариант FileOutputStream байтового потока OutputStream.

Здесь обработка исключений несколько сложнее, чем в случае чтения. Дело в том, что операционная система или реализация FileOutputStream для конкретной платформы могут кэшировать операции с диском. Например, данные, посылаемые в файл методом f.write(), могут на самом деле накапливаться в некотором буфере, и только при закрытии файла действительно записываться на диск. Это означает, что потенциальные ошибки на этапе закрытия файла столь же важны, что и ошибки в процессе работы метода f.write(). Игнорировать их, как мы делали в случае чтения, нельзя.

Приведенное выше решение игнорирует ошибки на этапе закрытия файла только в том случае, если какие-то ошибки уже имели место при записи методом f.write() – последние в любом случае окажутся не менее информативными. Если же метод f.write() отработал безошибочно, то закрытие файла выполняется вне каких-либо блоков try/catch, возможные исключения на этом этапе будут переданы наружу.

Копирование файла

Как скопировать файл в новый файл с другим именем? Конечно, если файл небольшой, его можно скопировать вполне тривиальным методом: вначале прочитать в массив байт, а затем записать под другим именем с помощью методов из предыдущего раздела. Но для многомегабайтных или гигабайтных файлов такой подход непригоден.

Очевидный путь решения задачи – последовательно читать файл небольшими блоками и записывать их в другой файл.

Вот предлагаемый текст решения:

  public static void copyFile(

    String source, String target)

    throws IOException

  {

    copyFile(new File(source),new File(target));

  }

  public static void copyFile(

    File source, File target)

    throws IOException

  {

    RandomAccessFile input=

      new RandomAccessFile(source,"r");

    RandomAccessFile output=

      new RandomAccessFile(target,"rw");

    try {

      byte[] buf = new byte[65536];

      long len= input.length();

      output.setLength(len);

      int bytesRead;

      while ((bytesRead=

        input.read(buf,0,buf.length))>0)

        output.write(buf,0,bytesRead);

    } catch (IOException e) {

      try {input.close();} catch (Exception e1) {};

      try {output.close();} catch (Exception e1) {};

      return;

    }

    try {input.close();} catch (Exception e) {};

    output.close();

  }

Логика обработки исключений здесь подобна логике, предложенной в предыдущем разделе. Ошибки на этапе чтения или записи (методы read()/write()) считаются приоритетными – при появлении таких ошибок дальнейшие исключения при закрытии файла игнорируются. В случае нормального выполнения всех методов read()/write() ошибки при закрытии исходного файла также игнорируются (ведь он уже успешно прочитан), а ошибки при закрытии выходного файла передаются наружу (пока файл не закрыт, нельзя быть уверенными, что он действительно появится на диске именно в таком виде – из-за возможного кэширования операций записи на диск).

Неочевидным в данном решении является использование класса RandomAccessFile вместо традиционных FileInputStream и FileOutputStream. На самом деле RandomAccessFile здесь использован ради возможности вызвать метод output.setLength(), отсутствующий в классе FileOutputStream.

Зачем нужен вызов метода output.setLength()? Казалось бы, это совершенно излишняя операция – ведь в итоге скопированный файл все равно будет иметь правильный размер.

Секрет здесь заключается в организации современных операционных систем, таких как Windows NT/2000/XP. Если сразу после создания файла «проинформировать» операционную систему о желаемой итоговой длине файла вызовом setLength(), то она сможет более эффективно выделить место на диске под этот файл, сведя к минимуму возможную фрагментацию диска. Если же наращивать файл постепенно, как получилось бы при отсутствии вызова setLength(), то операционная система будет резервировать под файл достаточно случайные свободные фрагменты дискового пространства. Возможно, первого выделенного свободного фрагмента не хватит для записи всего файла, и операционной системе придется выделять дополнительные фрагменты – файл окажется фрагментированным.

Другой плюс вызова setLength(): уже в самом начале копирования операционная система сможет сообщить об ошибке, если окажется, что места на дисковом носителе недостаточно для размещения полного файла.

С другой стороны, вызов setLength() приводит к тому, что в случае каких-либо ошибок результирующий файл получится либо полной (правильной) длины, но с неверным содержимым, либо длины 0. Как правило, в таком поведении нет ничего страшного – ошибки при копировании файлов вообще редкость. Тем не менее это следует иметь в виду.

(Вообще говоря, исходя из тех же самых соображений, можно было бы добавить вызов setLength() в процедуру записи бинарного файла, приведенную в первом разделе. Но во-первых, та процедура все-таки рассчитана на небольшие файлы, для которых фрагментация диска не столь существенна. Во-вторых, в той процедуре мы имеем дело с единственным вызовом метода write. Вероятнее всего, операционная система и так сумеет принять правильное решение о размещении сохраняемого блока на диске.)

Приведенную функцию копирования файла можно очевидным образом усовершенствовать – добавить стирание результирующего файла, если при копировании были исключения.

Чтение и запись текстового файла: кодировки

Работа с текстовыми файлами в Java далеко не так тривиальна, как с бинарными. Внутри Java текстовые данные всегда хранятся в кодировке Unicode – 16 бит на каждый символ. Но на диске текстовые файлы могут храниться в самых разных кодировках. Для преобразования кодировок файлов в Unicode и обратно нужны специальные классы-преобразователи: InputStreamReader и OutputStreamWriter.

Кодировка (encoding) – это способ описания (кодирования) всех возможных символов цепочками битов. «Ранние» кодировки, разработанные еще до появления Unicode, были рассчитаны на 8- или даже 7-битовое представление символа: с их помощью можно записать текст, содержащий максимум 256 (или соответственно 128) различных символов алфавита. Современные кодировки, такие как UTF-8 или UTF-16, резервируют более 8 бит на 1 символ и позволяют представить текст, содержащий любые из 65536 стандартных символов Unicode, в том числе иероглифы и всевозможные специальные знаки.

Каждая кодировка в Java идентифицируется своим именем. Стандартные библиотеки Java гарантированно «понимают» кодировки со следующими именами:

  • «ASCII» – 7-битовая кодировка ASCII для англоязычных текстов;
  • «Cp1252», «ISO8859_1» – 8-битовые расширения ASCII для западноевропейских языков;
  • «UnicodeBig», «UnicodeBigUnmarked», «UnicodeLittle», «UnicodeLittleUnmarked», «UTF-16» – основные варианты 16-битовой кодировки Unicude;
  • «UTF-8» – псевдо-8-битовая кодировка Unicode (в действительности на разные символы отводится разное число бит).

7- и 8-битовые кодировки предполагают, что каждый символ текста хранится в отдельном байте файла. В случае 7-битовой кодировки старший бит каждого байта попросту не используется (предполагается равным 0).

16-битовые кодировки Unicode отводят на каждый символ 16-битовое («короткое») слово файла. Друг от друга они отличаются наличием или отсутствием в файле специального 16-битового префикса, идентифицирующего формат Unicode, и порядком байт в 16-битовом слове.

Кодировки «UnicodeBig», «UnicodeLittle» и «UTF-16» предполагают, что первые 16 бит файла содержат префикс 0xFEFF, указывающий, что файл записан в формате Unicode. Кодировки «UnicodeBigUnmarked» и «UnicodeLittleUnmarked» интерпретируют первые 16 бит файла как первый символ текста. Кодировки «UnicodeBig» и «UnicodeBigUnmarked» предполагают порядок байт big-endian: старший байт каждого слова следует в файле перед младшим. Кодировки «UnicodeLittle» и «UnicodeLittleUnmarked» предполагают порядок little-endian, более привычный пользователям Intel-процессоров: младший байт каждого слова имеет в файле меньшее смещение от начала файла. При наличии префикса 0xFEFF порядок байт касается и способа записи этого префикса: в случае big-endian первым байтом файла будет 0xFE, в случае little-endian – 0xFF. Кодировка «UTF-16» не уточняет, какой именно будет порядок байт, но в этом случае префикс 0xFEFF является обязательным. Исходя из способа записи этого префикса, в готовом файле можно будет определить порядок байт в слове. Кодировка «UTF-8», строго говоря, не является 8-битовой, хотя и ориентирована на побайтовое хранение данных. В этой кодировке стандартные символы ASCII, имеющие в терминах Unicode коды 0..127, кодируются с помощью только одного байта, а все прочие символы «расходуют» 2 или более байт. Эта кодировка – наиболее универсальная и удобная в большинстве случаев. Для текстов, не содержащих национальных и специальных символов, кодировка «UTF-8» столь же компактна, как и традиционная ASCII, но при этом сохраняется принципиальная возможность записывать любые символы Unicode.

Чтение и запись текстового файла: алгоритмика

Прочитать целиком текстовый файл несколько сложнее, чем бинарный. В случае бинарного файла мы заранее знали размер требуемого буфера – он был равен длине файла. Число символов, содержащихся в текстовом файле, в общем случае невозможно определить, не прочитав файл, за исключением некоторых простых кодировок.

Наиболее естественно использовать для чтения текстового файла постепенно увеличивающийся буфер StringBuffer. Вот вариант готового решения:

  public static String loadFileAsString(

    File file, String encoding)

    throws IOException

  {

    InputStreamReader f= encoding==null?

      new FileReader(file):

      new InputStreamReader(

        new FileInputStream(file),encoding);

    StringBuffer sb= new StringBuffer();

    try {

      char[] buf= new char[32768];

      int len;

      while ((len=f.read(buf,0,buf.length))>=0) {

        sb.append(buf,0,len);

      }

      return sb.toString();

    } finally {

      try {f.close();} catch (Exception e) {};

    }

  }

Прокомментирую основные тонкости.

Прежде всего нужно обратить внимание на параметр encoding. Чтобы прочитать текстовый файл, необходимо указать кодировку, в которой он записан. Функция допускает передачу null в качестве encoding, но это всего лишь означает, что файл будет прочитан в текущей кодировке операционной системы. Скажем, файл с русским текстом, записанный в традиционной для России кодировке Windows (формальное название «Cp1251»), прочитается правильно только при условии, что текущим языковым стандартом (Regional Options) является русский.

Все это означает, что параметр encoding можно не указывать в единственном случае: когда предполагается, что файл записан в текущей кодировке операционной системы. Если файл записан в какой-то другой кодировке, то он может быть прочитан в виде совершенно непредсказуемого «мусора». Это вполне допустимо, например, если речь идет о написании простого одноплатформенного текстового редактора типа Notepad.

Внимание! Передавая null в качестве encoding, не следует полагаться, что латинские символы из стандарта ASCII-7 (с кодами 0..127), если таковые в файле имеются, будут гарантированно прочитаны правильно. Более того, теоретически возможна ситуация, когда файл, содержащий только символы с кодами 0..127, будет прочитан неверно! В принципе в какой-то операционной системе может оказаться, что текущей кодировкой является UTF-8, UTF-16 или какая-нибудь новая кодировка, которая будет разработана в будущем и которая кодирует символы совершенно непредсказуемым способом. В данный момент на известных мне версиях Windows все кодировки, предлагаемые операционной системой, хранят латинские символы «как есть», согласно стандарту ASCII-7. Но никто не может гарантировать, что так будет всегда. Чтобы правильно читать файлы с латинскими символами, записанные в традиционном формате ASCII, нужно явно указывать кодировку «ASCII» или одно из стандартных 8-битовых расширений: «Cp1252» либо «ISO8859_1».

Следующее замечание касается начальной емкости StringBuffer (необязательный параметр конструктора). Можно было бы попытаться оценить требуемую емкость, исходя из длины файла и заказанной кодировки. Но для сложных кодировок здесь легко ошибиться, заказав слишком много памяти, особенно учитывая возможное появление новых кодировок в будущем. А это чревато возникновением ошибки «Out of memory», даже когда на самом деле виртуальной памяти Java достаточно для размещения файла. Ускорение же, которого можно добиться таким способом, крайне незначительно и теряется на фоне «расшифровки» сложных кодировок и дисковых операций чтения файла.

Описанную функцию loadFileAsString можно дополнить версиями, читающими файл в виде массива символов, а также работающими с именем файла вместо объекта File:

  public static String loadFileAsString(

    String fileName, String encoding)

    throws IOException

  {

    return loadFileAsString(

      new File(fileName),encoding);

  }

  public static char[] loadFileAsChars(

    String fileName, String encoding)

    throws IOException

  {

    return loadFileAsChars(

      new File(fileName),encoding);

  }

  public static char[] loadFileAsChars(

    File file, String encoding)

    throws IOException

  {

    String buf= loadFileAsString(file,encoding);

    char[] result= new char[buf.length()];

    buf.getChars(0,result.length,result,0);

    return result;

  }

  public static void loadFileAsChars(

    File file, String encoding, char[] buf)

    throws IOException

  {

    loadFileAsChars(

      file,encoding,buf,0,buf.length);

  }

  public static void loadFileAsChars(

    File file, String encoding,

    char[] buf, int off, int len)

    throws IOException

  {

    InputStreamReader f= encoding==null?

      new FileReader(file):

      new InputStreamReader(

        new FileInputStream(file),encoding);

    try {

      f.read(buf,off,len);

    } finally {

      try {f.close();} catch (Exception e) {};

    }

  }

Те из приведенных функций, которые читают файл полностью, в конце концов сводятся к вызову описанной выше функции loadFileAsString. Последние две функции, заполняющие переданный им массив символов – целиком или заданный фрагмент, реализованы более просто. В этом случае заранее известно требуемое число символов, и нет необходимости использовать постепенно увеличивающийся StringBuffer.

Запись текстового файла – сравнительно простая задача, принципиально не отличающаяся от записи бинарного файла. Возможное решение:

  public static void saveFileFromString(

    String fileName, String encoding, String v)

    throws IOException

  {

    saveFileFromString(

      new File(fileName),encoding,v);

  }

  public static void saveFileFromString(

    File file, String encoding, String v)

    throws IOException

  {

    if (v==null) {

      file.delete(); return;

    }

    char[] buf= new char[v.length()];

    v.getChars(0,buf.length,buf,0);

    saveFileFromChars(file,encoding,buf);

  }

  public static void saveFileFromChars(

    String fileName, String encoding,

    char[] buf)

    throws IOException

  {

    saveFileFromChars(

      new File(fileName),encoding,buf);

  }

  public static void saveFileFromChars(

    File file, String encoding, char[] buf)

    throws IOException

  {

    if (buf==null) {

      file.delete(); return;

    }

    saveFileFromChars(

      file,encoding,buf,0,buf.length);

  }

  public static void saveFileFromChars(

    File file, String encoding,

    char[] buf, int off, int len)

    throws IOException

  {

    if (buf==null) {

      file.delete(); return;

    }

    OutputStreamWriter f= encoding==null?

      new FileWriter(file):

      new OutputStreamWriter(

        new FileOutputStream(file),encoding);

    try {

      f.write(buf,off,len);

    } catch (IOException e) {

      try {f.close();} catch (Exception e1) {};

      return;

    }

    f.close();

  }

Как и в случае чтения файла, параметр encoding в этих функциях может быть равен null, в этом случае файл будет записан в текущей кодировке операционной системы. Все функции в конце концов сводятся к вызову последней функции, записывающей фрагмент массива символов. Логика исключений полностью соответствует логике, использованной в первом разделе для записи бинарного файла.

Небольшой нюанс. Приведенные выше функции выполняют специальное действие – стирание файла, если в качестве данных (строки или буфера) им передано значение null. В моей практике такое поведение оказалось довольно удобным. Действительно, при хранении текстовых данных в файлах может возникнуть ситуация «данные отсутствуют», которую проще всего идентифицировать с помощью значения null строковой переменной. В этом случае стереть файл (если таковой существует) – самое логичное решение. Проверять наличие файла перед вызовом метода delete() не нужно: при отсутствии файла (или при возникновении других проблем) метод delete() возвращает false, не порождая никаких исключений.

Путешествие по файловой системе: основные трудности

Средства для анализа файловой системы: для работы с каталогами и путями, определения системных свойств файлов, работы с файлами-ссылками (links) и пр. в Java, к сожалению, реализованы довольно непоследовательно.

Пакет для работы с файлами java.io.* был разработан в те времена, когда на рынке господствовали MS-DOS, ранние версии Windows и всевозможные варианты UNIX. В результате этот пакет хорошо «справляется» только с простейшими путями к файлам, подчиненными жесткой иерархии в соответствии с идеологией UNIX (эту идеологию унаследовала MS-DOS и вслед за ней Windows). А именно: имена файлов и подкаталогов, лежащих в некотором каталоге, образуются добавлением их имен к имени этого каталога через символ-разделитель File.separator («» – для Windows, «/» – для UNIX). В корне иерархии должен лежать общий для всех корневой каталог «/» – в случае UNIX, в случае MS-DOS и Windows – корневой каталог диска «C:», «A:» или аналогичный. Все средства объекта java.io.File, предназначенные для работы с путями, такие как File.getParent(), рассчитаны на эту логику.

В то же время все современные версии Windows с точки зрения пользователя организуют свою файловую систему совершенно иначе. Корнем иерархии обычно является «рабочий стол» («Desktop»). Его дочерними подкаталогами (или, если быть точным, дочерними узлами иерархии) являются «компьютер» («My Computer»), «папка с документами» («My Documents»), подкаталог для доступа к локальной сети («My Network Places»). Дисковые накопители появляются уже как дочерние узлы «My Computer».

При этом, разумеется, маршрут вроде: DesktopMy ComputerLocal Disk (C:)подкаталог в Windows никакого смысла не имеет.

Тем не менее для большинства приложений такая организация файловой системы никак не конфликтует с более простой логикой пакета java.io.* Дело в том, что «на нижнем уровне» с точки зрения функций работы с файлами даже самые последние версии Windows сохранили старую, унаследованную от MS-DOS структуру файловой системы: в корне находятся диски «C:», «A:» и т. д. Каталоги типа «Desktop» и «My Documents» соответствуют некоторым каталогам главного диска операционной системы: в случае Windows 2000/XP они расположены в каталоге текущего пользователя внутри каталога «Documents and Settings». Любой файл или «обычный» каталог таким образом имеет вполне традиционный UNIX-подобный «низкоуровневый» маршрут. Даже файлы из локальной сети Windows имеют традиционные UNIX-подобные «низкоуровневые» маршруты типа:

"имя_компьютерапсевдоним_подкаталогаобычный_маршрут..."

На основе такого маршрута можно создать работоспособный объект java.io.File и в случае файла передать его прочим классам пакета java.io.* для работы с этим файлом. Для такого маршрута можно традиционными средствами класса java.io.File перейти к родительскому каталогу (File.getParent()), и получится вполне корректный новый маршрут, если только текущий маршрут не является корнем диска или верхнеуровневым псевдонимом подкаталога для компьютера из локальной сети. Наконец, если экземпляр класса java.io.File соответствует обычному каталогу (в том числе корню диска), то традиционные средства этого класса позволяют корректно получить все подкаталоги и файлы, лежащие внутри него, и работать с ними.

Пока приложение нуждается только в манипуляциях с файлами внутри некоторого обычного каталога (имя которого получено из диалога выбора файла или из конфигурационного файла), проблем не возникает. Такая ситуация достаточно типична для «невизуальных» и даже для многих визуальных приложений, в которых вся работа с диском сводится к манипуляциям с файлами внутри некоторого специального каталога, отведенного под это приложение. Но если нужно, например, показать пользователю полное дерево всех доступных файлов, включая сетевое окружение, причем стандартные диалоги выбора файла почему-либо не устраивают, – тут средства пакета java.io.* недостаточны. Другой пример нехватки возможностей java.io.*: этот пакет не способен определить каталог документов текущего пользователя Windows 2000/XP, а это очень неплохая «точка отсчета» для размещения рабочих файлов приложения.

Чтобы поддержать современные файловые системы, компания Sun разработала новый отдельный пакет javax.swing.filechooser.* Он размещен внутри графической библиотеки Swing и рассчитан на использование совместно с диалогом выбора файла javax.swing.JFileChooser. Тем не менее возможности, предлагаемые этим пакетом, точнее, классом javax.swing.filechooser.FileSystemView, не имеют никакого отношения к визуальному интерфейсу. В действительности методы этого класса куда логичнее смотрелись бы в пакете java.io.*

Сегодня средства для анализа и управления файловой системой в Java распределены по двум классам: java.io.File и javax.swing.filechooser.FileSystemView. Классы эти непохожи по организации и частично перекрывают возможности друг друга, так что порой не так просто определить, какой из них выбрать.

Попробуем разобраться в этих двух классах.

Путешествие по файловой системе: класс java.io.File

Класс java.io.File, вопреки названию, является представлением не собственно файла, а маршрута к файлу или подкаталогу. (И почему его не назвали, скажем, «Path»?) Создание экземпляра File никак не связано с созданием файла, для работы с файлами служат потоки ввода-вывода. По сути, класс File можно считать специализированным вариантом класса String, рассчитанным на работу с маршрутами к файлам/подкаталогам и, что отличает его от String, допускающим создание наследников.

Экземпляр File обычно создается одним из конструкторов:

  public File(String pathname)

  public File(File parent, String child)

  public File(String parent, String child)

Смысл их достаточно очевиден. В качестве pathname (или parent) нужно указывать маршрут к файлу/подкаталогу (соответственно к каталогу, содержащему файл/подкаталог), например, «/tmp/a», «myfile.txt» или «c:abc.java». Путь (маршрут) считается абсолютным, если начинается со слэша (/ или ) или c буквы диска с последующим слэшем в случае DOS/Windows. В противном случае путь считается относительным и отсчитывается от текущего каталога операционной системы.

Разработчики Java разрешили при создании экземпляра File для разделения каталогов в маршруте всегда использовать «универсальный» прямой слэш /, помимо стандартного разделителя File.separator ( в случае Windows).

Преобразование объекта File в строку (метод toString() или полностью эквивалентный ему getPath()), как и следовало ожидать, возвращает исходный маршрут, который был передан в конструктор. Но все прямые слэши при этом заменяются на File.separator. Вообще, все методы класса File, возвращающие путь к файлу в виде строки, возвращают его в «нормализованном» виде, т.е. с заменой всех прямых слэшей на File.separator.

Если объект File содержит относительный маршрут к файлу/подкаталогу, то его можно преобразовать в абсолютный методами getAbsoluteFile() или getAbsolutePath() (первый возвращает File, второй – String). Все, что делают эти методы, – добавляют перед началом относительного пути маршрут к текущему каталогу. Эти методы исключений не порождают, но могут вызвать обращение к диску для определения текущего каталога.

Два похожих метода – getCanonicalFile() и getCanonical-Path() – делают несколько больше. Пользуясь средствами операционной системы, они пытаются привести имя файла/подкаталога к единообразному виду, одинаковому для одинаковых реальных положений файла/подкаталога в файловой системе. Например, на моем компьютере (Windows XP) c текущим основным диском E: вызовы:

 (new File(" m/../tm/1.txt")).getCanonicalPath()

и

(new File("e://tm/1.txt")).getCanonicalPath()

возвращают одинаковую строку:

  E: m1.txt

Каталога E: m у меня на самом деле нет. Если бы он был, но реально назывался «TM» (заглавными буквами), то указанные вызовы вернули бы строку:

  E:TM1.txt

так как с точки зрения Windows каталоги «E: m» и «E:TM» идентичны. Методы getCanonicalFile() и getCanonicalPath(), разумеется, не гарантируют получения одного результата для всех возможных маршрутов к одному и тому же файлу/подкаталогу. Скажем, в случае Windows они «ничего не знают» о маршрутах в локальной сети типа «имя_моего_компьютераe m1.txt». Эти методы могут порождать исключение IOException.

Главные методы класса File, обеспечивающие «навигацию» по файловой системе, следующие:

  public File getParentFile()

  public String getParent()

  public String getName()

  public File[] listFiles()

  public File[] listFiles(FileFilter filter)

  public String[] list()

  public String[] list(FilenameFilter filter)

  public static File[] listRoots()

А также уже упомянутые конструкторы:

  public File(File parent, String child)

 public File(String parent, String child)

Смысл их достаточно понятен, а точное поведение можно узнать из документации. Я бы только обратил внимание (это легко не заметить при изучении документации), что методы list возвращают в массиве строк короткие имена файлов (не включающие путь к родительскому каталогу), в то время как listFiles возвращают маршруты, содержащие в начале родительский маршрут. Причем, если объект File, у которого вызывается один из методов listFiles, представляет относительный маршрут, то пути в полученном массиве маршрутов тоже будут относительными.

Как раз эти методы, прекрасно работавшие в DOS и UNIX, безнадежно «морально устарели» в современных версиях Windows. «Подъем» к родителю по файловому дереву означает банальное удаление последнего элемента в цепочке каталогов, разделенных слэшем или File.separator.

Например, у каталога «имя_моего_компьютераe» (диск E моего компьютера в виде «сетевого» маршрута) родителем оказывается «имя_моего_компьютера», а у него, в свою очередь, «» – «фальшивые» каталоги, с которыми класс File не может сделать ничего полезного. Попытка узнать все корневые каталоги вызовом listRoots() даже в современной Windows XP вернет список корневых каталогов всех дисков, полностью игнорируя такие понятия, как «сетевое окружение», «рабочий стол», «мои документы». Ниже мы обсудим «современную» альтернативу – класс javax.swing.filechooser.FileSystemView.

Кроме основной задачи – работы с маршрутами – класс File реализует некоторые вспомогательные операции над физическими файлами: проверку существования, удаление, создание файла или подкаталога, переименование и т. п. Для этого служат методы:

  public boolean exists()

  public boolean isDirectory()

  public boolean isFile()

  public boolean isHidden()

  public long lastModified()

  public long length()

  public boolean canRead()

  public boolean canWrite()

  public boolean delete()

  public void deleteOnExit()

  public boolean createNewFile()

  public boolean mkdir()

  public boolean mkdirs()

  public boolean renameTo(File dest)

  public boolean setLastModified(long time)

  public boolean setReadOnly()

Все эти методы, кроме createNewFile(), не порождают исключений.

Как видите, набор возможностей для современных операционных систем исключительно бедный, учитывая, что это единственный класс пакета java.io.*, предлагающий подобный сервис. (Мы не считаем класс java.io.FileSystem, обеспечивающий то же самое, но несколько менее удобным способом.) Удивительно, что метод isHidden() не имеет парного setHidden(), а setReadOnly() работает только «в одну сторону», не позволяя снять атрибут «read-only».

Еще класс File обеспечивает несколько вспомогательных возможностей – создание временных файлов и преобразование маршрута к формату URI, и наоборот. Также этот класс предоставляет строковые константы separator, pathSeparator и их синонимы типа char: separatorChar и pathSeparatorChar.

Путешествие по файловой системе: класс javax.swing.filechooser.FileSystemView

Класс javax.swing.filechooser.FileSystemView – это библиотека методов для работы с объектами типа File. Эта библиотека, прежде всего, позволяет перемещаться по файловой системе (переходить к «родителю» и получать список «детей»), причем в современном стиле: корнем иерархии для Windows служит рабочий стол «Desktop», в нем имеется выход в «My Computer» и локальную сеть, и т. д. Кроме того, библиотека FileSystemView открывает доступ к дополнительным свойствам файлов, подкаталогов и иных, «нефайловых» элементов файловой системы типа «Desktop».

Чтобы использовать FileSystemView, нужно вначале получить экземпляр библиотеки с помощью статического метода getFileSystemView():

  FileSystemView fsv=FileSystemView.getFileSystemView();

Это единственный способ получить экземпляр: класс FileSystemView – абстрактный и не имеет открытых наследников. В действительности указанный метод возвращает экземпляр одного из закрытых классов-наследников, разных для разных операционных систем.

Получив экземпляр, можно использовать следующие методы:

  public File getParentDirectory(File dir)

  public File getChild(File parent, String fileName)

  public File createFileObject(File dir, String filename)

  public File createFileObject(String path)

  public File[] getFiles(File dir, boolean useFileHiding)

  public File[] getRoots()

Эти методы «перекрывают» основные методы и конструкторы класса File, предназначенные для «навигации» по файловой системе. Данные методы работают уже в «современном» стиле. Если мы попытаемся подняться от любого файла (созданного либо через createFileObject, либо конструктором класса File) до корня иерархии, последовательно вызывая метод getParentDirectory, то под Windows мы в конце концов доберемся до каталога «Desktop». Метод getRoots также честно возвращает этот единственный каталог. Если мы будем считывать дерево каталогов, вызывая getFiles для корневого каталога «Desktop», затем для его элементов, затем для их элементов и т. д., мы увидим примерно то же самое, что видит пользователь в любом стандартном диалоге выбора файла Windows: «My Com-puter», «My Network Places» и т. д.

Очень поучительно написать тест, считывающий «верхние этажи» дерева каталогов с помощью этих методов и распечатывающий для каждого элемента (объекта File) результаты вызовов toString(), getAbsolutePath(), getCano-nicalPath(), getParent(), listFiles(), а также имя класса getClass(). Также интересно распечатать аналогичную информацию для каталогов, получаемых подъемом к корню вызовами getParentDirectory от произвольных обычных маршрутов, созданных конструктором класса File.

Оказывается, что классы объектов File, полученных таким способом, в действительности часто являются наследниками стандартного класса java.io.File, чаще всего (в случае Windows) это класс sun.awt.shell.Win32ShellFolder. Этого следовало ожидать. Обычный класс File в принципе не способен представить такую сущность, как «My Computer». Действительно, практически любой маршрут, хранящийся в объекте File, который мы бы могли придумать для обозначения «My Computer», одновременно соответствовал бы какому-нибудь вполне допустимому файлу, а его методы вроде getParent() вообще возвращали бы ерунду.

На самом деле любой вызов getParentDirectory возвращает для Windows объект «внутреннего» класса фирмы Sun sun.awt.shell.Win32ShellFolder (наследника File), причем если исходный маршрут был относительным, он автоматически преобразуется в абсолютный. У такого объекта методы getParent() и listFiles() работают уже «правильно», так же, как getParentDirectory и getFiles. Метод list(), судя по всему, возвращает имена только для «обычных» файлов/подкаталогов, опуская имена дисков или элементов типа «My Computer».

Методы getPath() и getAbsolutePath() для «нефайловых» объектов типа «Desktop» возвращают путь к каталогу, который в операционной системе соответствует данной высокоуровневой сущности, при отсутствии такового (как в случае «My Computer») – некоторые абстрактные имена.

Описанные свойства наследников класса File, возвращаемых методами FileSystemView, выяснены из тестирования на версии JDK 1.4.1. В документации они, судя по всему, не описаны, так что полагаться на них не стоит. Для перемещения по файловой системе следует использовать документированные методы FileSystemView getParentDirectory, getChild, getFiles...

Важное замечание: в отличие от класса File, библиотека FileSystemView рассчитана на работу не с произвольными маршрутами, а только с путями к реально существующим файлам или подкаталогам. Например, метод getParentDirectory для несуществующего файла/подкаталога (созданного конструктором File для маршрута, не соответствующего реальному файлу или каталогу) попросту вернет null. Метод File.getParent() в этом случае на общих основаниях «отрезал» бы от маршрута конечную часть, начиная от последнего слэша.

Кроме описанных, библиотека FileSystemView предлагает также следующие методы:

    public File getDefaultDirectory()

  public File getHomeDirectory()

Первый из них под Windows возвращает каталог «My Documents», а второй – каталог «Desktop».

  public boolean isFileSystem(File f)

  public boolean isRoot(File f)

  public boolean isFileSystemRoot(File dir)

  public boolean isDrive(File dir)

  public boolean isFloppyDrive(File dir)

  public boolean isComputerNode(File dir)

Эти методы (в дополнение к традиционным методам isDirectory() и isFile() класса File) позволяют распознать «специальные» узлы файловой иерархии, не соответствующие обычным файлам или подкаталогам. Точный их смысл описан в документации на FileSystemView. Все «обычные» файлы и подкаталоги, которые можно корректно представить классом File, относятся к категории isFileSystem. Часто бывает полезно сочетание свойств:

  fsv.isFileSystem(f)

  && !fsv.isRoot(f)

  && !fsv.isFileSystemRoot(f);

В этом случае есть гарантия, что не только сам маршрут f, но и его родитель является вполне «добропорядочным» маршрутом, допускающим представление с помощью класса File.

    public boolean isHiddenFile(File f)

Если судить по документации, это просто дубликат обычного f.isHidden().

    public Boolean isTraversable(File f)

Этот метод обобщает f.isDirectory() на случай «необычных» элементов файловой системы, таких как соседние компьютеры в локальной сети. Несколько необычный тип результата, видимо, выбран по аналогии с другим классом javax.swing.filechooser.FileView, обслуживающим нужды визуального компонента JFileChooser.

  public String getSystemDisplayName(File f)

  public javax.swing.Icon getSystemIcon(File f)

  public boolean isParent(File folder, File file)

Этот метод не имеет аналогов в классе File. В рамках пакета java.io.* для аналогичных целей должен был бы использоваться примерно такой вызов:

  file.getParent().equals(folder.getPath())

(возможно, с предварительным приведением маршрутов к каноническому виду методами getCanonicalPath). При работе с более сложными современными файловыми системами подобная проверка строк была бы некорректна. Действительно, folder.getPath() может вернуть «My Computer» как для настоящего элемента «My Computer» – родителя каталога «C:», так и для подкаталога с именем «My Computer» в текущем каталоге Windows. Попытка использовать getCanonicalPath не привела бы к успеху – для «My Computer» метод getCanonicalPath порождает исключение, так как никакому осмысленному каталогу подобный элемент файловой системы не соответствует.

На самом деле современная реализация метода isParent (JDK 1.4.1) устроена крайне неэффективно. Если folder – наследник некоего внутреннего класса sun.awt.shell.Shell-Folder (именно такие наследники получаются для всех каталогов, возвращаемых методами FileSystemView), то метод isParent, не мудрствуя лукаво, получает полный список всех «детей» folder (вызовом getFiles) и поочередно сравнивает объект file с каждым из них.

Вот типичный пример скрытой квадратичности, глубоко «закопанной» в исходниках библиотечных модулей. В частности, такая реализация порождает ужасающее «торможение» визуального компонента JFileChooser при попытке просматривать с помощью клавиатуры каталог из сотен или тысяч файлов. Связано это с тем, что любое перемещение курсора по каталогу приводит к вызову метода setSelectedFile, делающему новый файл активным. Этот метод проверяет (вызовом isParent), точно ли текущий выбранный каталог является «родителем» по отношению к новому файлу, и если это не так, изменяет текущий каталог. Действие в данной ситуации, вообще говоря, бессмысленное. (Это издержки общей организации модуля JFileChooser: один и тот же метод setSelectedFile используется и для управления компонентом «снаружи», когда текущий каталог действительно может измениться, и при реакции на стрелки «вверх»/«вниз».) Проверка нового выбранного файла методом isParent требует полного перебора всех файлов текущего каталога, так как каталоги в JFileChooser всегда представлены наследниками File, сгенерированными методами FileSystemView. В результате каждое нажатие на стрелку «вверх»/«вниз» приводит к линейному перебору каталога, а полная прокрутка каталога с помощью стрелок на клавиатуре означает квадратичное число операций.

Аналогичное «скрытое» торможение легко можно получить, легкомысленно пользуясь методами getDefault-Directory() и getHomeDirectory(), которые могут понадобиться и в совершенно «невизуальной» программе. Эти методы тоже возвращают наследников ShellFolder, и для них isParent работает медленно. В моей практике был случай, когда программа начала загружаться в десятки раз медленнее (несколько минут) только из-за того, что в момент старта был добавлен простой анализ содержимого каталога getDefault-Directory(). Причина оказалась именно в методе isParent, превратившего нормальный линейный алгоритм анализа в квадратичный. Самое неприятное, что проблема проявлялась только в случае, когда каталог «My Documents» содержал много элементов – ситуация типичная для рядового пользователя, но сравнительно редкая для хорошо структурированных файловых систем профессионалов.

Надо надеяться, что в будущих версиях Java фирма Sun оптимизирует метод isParent, устранив оттуда перебор. Для подавляющего числа ситуаций, когда каталог вполне «нормальный» (что можно определить вызовом isFileSystem), перебор совершенно не нужен. Пока что я рекомендую, если это возможно и допустимо логикой программы, всегда конвертировать каталоги, полученные от класса FileSystemView, в обыкновенные объекты File:

  f= new File(f.getPath());

Например, это можно проделать с каталогом getDefaultDirectory(), если программе нужно просто прочитать оттуда какие-то конфигурационные или другие файлы. Кроме того, это почти всегда можно сделать, если выполнено условие:

  fsv.isFileSystem(f)

Путешествие по файловой системе: недокументированный класс sun.awt.shell.ShellFolder

Как оказывается, даже с помощью класса FileSystemView нельзя написать программу, адекватно работающую с файловыми системами современной Windows XP. В этой версии Windows большое значение имеют «ссылки» (links) или «ярлыки». В ранних версиях Windows это были файлы с расширением «.lnk», сейчас к ним добавились специально организованные подкаталоги. С точки зрения пользователя «ярлык», ссылающийся на некоторый каталог, должен вести себя подобно этому каталогу. Щелчок по «ярлыку» (например, в диалоге выбора файла) должен открывать этот каталог. Доступ к локальной сети из основной файловой иерархии в Windows XP организован именно через «ярлыки»: компьютеры пользователей-«соседей» («My Network Places») представлены маленькими виртуальными подкаталогами-«ярлыками» в локальном дисковом каталоге текущего пользователя. (Эти подкаталоги физически находятся внутри скрытого подкаталога NetHood, лежащего в главном каталоге пользователя.)

К сожалению, в текущей версии Java не предусмотрено никаких документированных средств для работы с «ярлыками». С точки зрения стандартных библиотек Java файл с расширением «.lnk» – это просто некоторый двоичный файл с недокументированным строением, а подкаталог-«ярлык» из «My Network Places», ссылающийся на другой компьютер – это самый обычный подкаталог с двумя служебными файлами. В результате дерево файловой системы, построенное с помощью документированных библиотек Java в Windows XP, оказывается принципиально неполным – содержимое компьютеров локальной сети туда не попадает. Это особенно заметно при использовании стандартного диалога выбора файла JFileChooser – компьютеры из «My Network Places» там видны, но попытка зайти на них заканчивается, мягко говоря, странно.

К счастью, фирма Sun уже реализовала механизм обработки файлов-«ссылок» Microsoft Windows, хотя пока не документировала его. Это уже упоминавшийся выше класс sun.awt.shell.ShellFolder. Помимо прочих методов, имеющих документированные эквиваленты в классе FileSystemView, класс ShellFolder содержит следующие два метода:

  public abstract boolean isLink();

  public abstract ShellFolder getLinkLocation()

    throws FileNotFoundException

Вот как можно ими пользоваться:

  public static boolean isLink(File f) {

    try {

      return sun.awt.shell.ShellFolder

        .getShellFolder(f).isLink();

    } catch (FileNotFoundException e) {

      return false;

    }

  }

  public static File getLinkLocation(File f)

    throws FileNotFoundException

  {

    File result= sun.awt.shell.ShellFolder

      .getShellFolder(f).getLinkLocation();

    if (result==null ||

      result.getPath().trim().length()==0)

      throw new FileNotFoundException(

        "Incorrect link - it is empty");

    return result;

  }

Надо надеяться, что скоро эти методы станут документированными или, что более вероятно, получат документированные эквиваленты в классе FileSystemView. Вероятно, что одновременно компания Sun исправит диалог выбора файла JFileChooser, с тем чтобы он начал корректно работать с локальной сетью Windows XP. Пока что можно при желании «исправить» поведение JFileChooser для локальной сети и других ярлыков XP самостоятельно. Простейший способ это сделать – реализовать собственного наследника JFileChooser и перекрыть в нем методы isTraversable, setCurrentDirectory, getCurrentDirectory. Приблизительно это выглядит так (здесь isLink и getLinkLocation – приведенные выше методы):

  public boolean isTraversable(File f) {

    if (super.isTraversable(f)) return true;

    if (f!=null && isLink(f)) {

      try {

        return super.isTraversable(

          getLinkLocation(f));

      } catch (FileNotFoundException e) {

      }

    }

    return false;

  }

  public void setCurrentDirectory(File f) {

    if (f!=null && isLink(f)) {

      try {

        super.setCurrentDirectory(

          getLinkLocation(f));       

      } catch (FileNotFoundException e) {

      }

    }

    super.setCurrentDirectory(f);

  }

  public File getCurrentDirectory() {

    File dir= super.getCurrentDirectory();

    if (dir!=null && isLink(dir)) {

      try {

        return getLinkLocation(dir);

      } catch (FileNotFoundException e) {

      }

    }

    return dir;

  }

Даже прибегая к недокументированным функциям, на Java нельзя реализовать многие операции с файлами, имеющиеся в современных операционных системах. Например, из Java нельзя читать и управлять правами доступа файловой системы NTFS. Остались «за бортом» многие понятия Windows, такие как каталоги «My Pictures» или «My Music». Если все-таки подобные возможности необходимы, то, к сожалению, единственный выход – реализовывать недостающие функции самостоятельно в виде native-кода для всех операционных систем, под которыми предполагается эксплуатировать программу.

Заключение

Мы рассмотрели ряд основных задач, возникающих при работе с файлами в Java, а также всевозможные нюансы и «подводные» камни, связанные с их решением. Я постарался приводить только тщательно протестированные и продуманные фрагменты кода. К сожалению, этот код был должным образом протестирован только под Windows. Не исключено, что файловые системы UNIX или Macintosh могут добавить свои сюрпризы к уже описанным здесь.

В любом случае, надеюсь, приведенная информация послужит хорошим дополнением к документации для всех, кто хочет достаточно полно и грамотно взаимодействовать с файловой системой из Java.


Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-45
E-mail: sa@samag.ru