В приведенном выше примере поток ввода из файла закрывается после того, как чтение данных из файла завершается в блоке try. Такой способ оказывается удобным не всегда, и поэтому в Java предоставляется более совершенный и чаще употребляемый способ. А состоит он в вызове метода close () в блоке finally. В этом случае все методы, получающие доступ к файлу, помещаются в блок try, а для закрытия файла используется блок finally. Благодаря этому файл закрывается независимого от того, как завершится блок try. Если продолжить предыдущий пример, то блок try, в котором выполняется чтение из файла, можно переписать следующим образом:try { do { i = fin.read(); if(i != -1) System.out.print((char) i) ; } while(i != —1) ;} catch(IOException exc) { System.out.println("Error Reading File"); // Блок finally используется для закрытия файла.} finally { // закрыть файл при выходе из блока try. try { fin.close (); } catch(IOException exc) { System.out.println("Error Closing File"); }}
Преимущество рассмотренного выше способа состоит, в частности, в том, что если программа, получающая доступ к файлу, завершается аварийно из-за какой-нибудь ошибки ввода-вывода, генерирующей исключение, файл все равно закрывается в блоке finally. И если с аварийным завершением простых программ, как в большинстве примеров в этой книге, из-за неожиданно возникающей исключительной ситуации еще можно как-то мириться, то в крупных программах подобная ситуация вряд ли вообще допустима. Именно ее и позволяет исключить блок finally.
Иногда оказывается проще заключить в оболочку те части программы, в которых открывается файл, чтобы получить доступ к нему из единственного блока try, не разделяя его на два блока, а для закрытия файла использовать отдельный блок finally. В качестве примера ниже приведена переделанная версия рассмотренной выше программы ShowFile./* В этой версии программы отображения текстового файла код,открывающий файл и получающий к нему доступ, заключаетсяв единственный блок try. А закрывается файл в блоке finally.*/import java.io.*;class ShowFile { public static void main(String args[]) { int i; FilelnputStream fin = null; // Прежде всего следует убедиться, что файл был указан, if (args.length != 1) { System.out.println("Usage: ShowFile filename"); return; } // В следующем коде открывается файл, из которого читаются // символы до тех пор, пока не встретится знак EOF, а затем // файл закрывается в блоке finally, try { fin = new FilelnputStream(args[0]); do { i = fin.read() ; if(i != -1) System.out.print((char) i); } while(i != -1); } catch(FileNotFoundException exc) { System.out.println("File Not Found."); } catch(IOException exc) { System.out.println("An I/O Error Occurred"); } finally { // Файл закрывается в любом случае, try { if (fin != null) fin.closeO; } catch(IOException exc) { System.out.println("Error Closing File"); } } }}
Обратите внимание на то, что переменная fin инициализируется пустым значением null. А в блоке finally файл закрывается только в том случае, если значение переменной fin не является пустым. Такой способ оказывается вполне работоспособным, поскольку переменная fin не будет содержать пустое значение лишь в том случае, если файл был успешно открыт. Следовательно, метод close () не будет вызываться, если во время открытия файла возникнет исключение.
В приведенном выше примере блок try/catch можно сделать более компактным. Ведь исключение FileNotFoundException является подклассом исключения IOException, и поэтому его не нужно перехватывать отдельно. В качестве примера ниже приведен блок оператора catch, которым можно воспользоваться для перехвата обоих этих исключений, не прибегая к перехвату исключения FileNotFoundException в отдельности. В данном случае выводится стандартное сообщение о возникшем исключении с описанием характера ошибки.} catch(IOException exc) { System.out.println("I/O Error: " + exc);} finally {...В рассматриваемом здесь способе любая ошибка, в том числе и ошибка открытия файла, будет обработана единственным оператором catch. Благодаря своей компактности именно такой способ применяется в большинстве примеров ввода-вывода, представленных в этой книге. Следует, однако, иметь в виду, что он может оказаться не вполне пригодным в тех случаях, когда требуется отдельно обрабатывать ошибку открытия файла, например, вследствие того, что пользователь введет имя файла с опечаткой. В подобных случаях рекомендуется выдать сначала приглашение правильно ввести имя файла, а затем перейти к блоку try для доступа к файлу.### Вывод в файлДля того чтобы открыть файл для вывода, следует создать объект типа FileOutputStream. Ниже приведены два наиболее часто употребляемых конструктора этого класса.
FileOutputStream(String имяфайла) throws FileNotFoundExceptionFileOutputStream(String имяфайлаг boolean append) throws FileNotFoundExceptionЕсли файл не может быть создан, возникает исключение FileNotFoundException. В первой форме конструктора при открытии файла удаляется существовавший ранее файл с таким именем. Вторая форма отличается наличием параметра append. Если этот параметр принимает логическое значение true, записываемые данные добавляются в конец файл. В противном случае старые данные в файле перезаписываются новыми.Для того чтобы записать данные в файл, следует вызвать метод write(). Наиболее простая форма этого метода приведена ниже,
void write(int byteval) throws IOExceptionЭтот метод записывает в поток байтовое значение, указанное в качестве параметра byteval. Несмотря на то что этот параметр объявлен как int, учитываются только 8 младших битов его значения. Если в процессе записи возникнет ошибка, будет сгенерировано исключение IOException.По завершении работы с файлом его нужно закрыть с помощью метода close(). Объявление этого метода выглядит следующим образом:
void close() throws IOExceptionПри закрытии файла освобождаются связанные с ним системные ресурсы, чтобы использовать их для работы с другим файлом. Процедура закрытия файла также гарантирует, что данные, оставшиеся в буфере, будут записаны на диск.В приведенном ниже примере программы осуществляется копирование текстового файла. Имена исходного и целевого файлов указываются в командной строке.
/ Копирование текстового файла.При вызове этой программы следует указать имя исходногои целевого файлов. Например, для копирования файла FIRST.TXTв файл SECOND.TXT в командной строке нужно указать следующее:java CopyFile FIRST.TXT SECOND.TXT/import java.io.*;class CopyFile { public static void main(String args[]) { int i; FilelnputStream fin; FileOutputStream fout;// Прежде всего следует убедиться, что оба файла были указаны,if(args.length !=2 ) { System.out.println("Usage: CopyFile From To"); return;}// открыть исходный файлtry { fin = new FilelnputStream(args[0] ) ;} catch(FileNotFoundException exc) { System.out.println("Input File Not Found"); return;}// открыть целевой файлtry { fout = new FileOutputStream(args[1]);} catch(FileNotFoundException exc) { System.out.println("Error Opening Output File"); // закрыть исходный файл try { fin.close (); } catch(IOException exc2) { System.out.println("Error closing input file."); } return;}// копировать файлtry { do { // Чтение байтов из одного файла и запись их в другой файл. i = fin.read(); if(i != -1) fout.write (i); } while(i != -1);} catch(IOException exc) { System.out.println("File Error");}try { fin.close () ;} catch(IOException exc) { System.out.println("Error closing input file.");}try { fout.close();} catch(IOException exc) { System.out.println("Error closing output file.");}
}}## Автоматическое закрытие файловВ примерах программ из предыдущего раздела метод с 1 о s е () вызывался явным образом для закрытия файла, когда он уже не был больше нужен. Подобным образом файлы закрывались с тех пор, как появилась первая версия Java. В итоге именно такой способ получил широкое распространение в существующих программах на Java. И до сих пор он остается вполне обоснованным и пригодным. Но в JDK 7 внедрено новое средство, предоставляющее другой, более рациональный способ управления ресурсами, в том числе и потоками файлового ввода-вывода, автоматизирующий процесс закрытия файлов. Этот способ основывается на новой разновидности оператора try, называемой оператором try с ресурсами, а иногда еще — автоматическим управлением ресурсами. Главное преимущество оператора try с ресурсами заключается в том, что он предотвращает ситуации, в которых файл (или другой ресурс) неумышленно остается неосвобожденным после того, как он уже больше не нужен. Как пояснялось ранее, если не позаботиться вовремя о закрытии файла в программе, это может привести к утечкам памяти и прочим осложнениям в работе программы.Ниже приведена общая форма оператора try с ресурсами