Главная страница

Ответы на вопросы по ревью 4. Java io. Ключевым понятием здесь является понятие потока


Скачать 1.93 Mb.
НазваниеJava io. Ключевым понятием здесь является понятие потока
Дата03.07.2022
Размер1.93 Mb.
Формат файлаdoc
Имя файлаОтветы на вопросы по ревью 4.doc
ТипДокументы
#623608
страница9 из 39
1   ...   5   6   7   8   9   10   11   12   ...   39

Чтение и запись в файлы из байтовых потоков


В Java предоставляется большое количество классов и методов, позволяющих читать и записывать данные в файлы. Разумеется, чаще всего приходится обращаться к файлам, хранящимся на дисках. В Java все файлы имеют байтовую организацию, и поэтому для побайтового чтения и записи данных в такие файлы предоставляются соответствующие методы. Следовательно, организовывать чтение и запись данных в файлы из байтовых потоков приходится довольно часто. Кроме того, для байтовых потоков ввода-вывода в файлы в Java разрешено создавать специальные оболочки в виде символьных объектов. Такие оболочки будут рассмотрены далее в этой главе.

Для того чтобы создать байтовый поток и связать его с файлом, следует воспользоваться классом FilelnputStream или FileOutputStream. А для открытия файла достаточно создать объект одного из этих классов, передав имя файла конструктору в качестве параметра. В открытый файл можно записывать данные или читать их из него.

Ввод данных из файла


Файл открывается для ввода созданием объекта типа FilelnputStream. Для этой цели чаще всего используется приведенная ниже форма объявления конструктора данного класса. FilelnputStream(String имя_файла) throws FileNotFoundException

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

Для чтения данных из файла служит метод read(). Ниже приведена форма объявления этого метода, которой мы будем пользоваться в дальнейшем,

int read() throws IOException

При каждом вызове метод read() читает байт из файла и возвращает его как целочисленное значение. По достижении конца файла этот метод возвращает значение -1. При возникновении ошибки метод генерирует исключение IOException. Как видите, в этой форме метод read() выполняет те же самые действия, что и одноименный метод для ввода данных с консоли.

Завершив операции с файлом, следует закрыть его с помощью метода close(), общая форма объявления которого выглядит следующим образом:

void close() throws IOException

При закрытии файла освобождаются связанные с ним системные ресурсы, чтобы использовать их для работы с другим файлом. Если же файл не будет закрыт, могут произойти “утечки памяти” из-за того, что часть памяти остается выделенной для неиспользуемых ресурсов. Ниже приведен пример программы, где метод read() используется для ввода содержимого текстового файла. Имя файла задается с помощью параметра в командной строке при запуске программы на выполнение. Полученные данные выводятся на экран. Обратите внимание на то, что ошибки ввода-вывода обрабатываются с помощью блока try/catch.

/* Отображение текстового файла.

При вызове этой программы следует указать имя файла,

содержимое которого требуется просмотреть.

Например, для вывода на экран содержимого файла TEST.TXT,

в командной строке нужно указать следующее:

java ShowFile TEST.TXT

*/

import java.io.*;

class ShowFile {

public static void main(String args[])

{

int i;

FilelnputStream fin;

// Прежде всего следует убедиться, что файл был указан,

if(args.length != 1) {

System.out.println("Usage: ShowFile File");

return;

}

try {

// Открытие файла.

fin = new FilelnputStream(args[0]);

} catch(FileNotFoundException exc) {

System.out.println("File Not Found");

return;

}

try {

// читать из файла до тех пор, пока не встретится знак EOF.

do {

// Чтение из файла.

i = fin.read();

if(i != -1) System.out.print((char) i) ;

// Если значение переменной i равно -1,значит,

// достингут конец файла.

} while (i != -1);

} catch(IOException exc) {

System.out.println("Error reading file.");

}

try {

// Закрытие файла.

fin.close();

} catch(IOException exc) {

System.out.println("Error closing file.");

}

}

}

В приведенном выше примере поток ввода из файла закрывается после того, как чтение данных из файла завершается в блоке 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 FileNotFoundException FileOutputStream(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 с ресурсами

try (описание_ресурса) { // использовать ресурс }

где описание_ресурса обозначает оператор, в котором объявляется и инициализируется конкретный ресурс, например файл. По существу, он содержит объявление переменной, в котором переменная инициализируется ссылкой на объект управляемого ресурса. По завершении блока try объявленный ресурс автоматически освобождается. Если этим ресурсом является файл, то он автоматически закрывается, что избавляет от необходимости вызывать метод close() явным образом. В блок оператора try с ресурсами могут также входить операторы catch и finally.
Оператор try с ресурсами можно применять только к тем ресурсам, в которых реализуется интерфейс AutoCloseable, определенный в пакете java. lang. Этот интерфейс внедрен в JDK 7, и в нем определен метод close(). Интерфейс AutoCloseable наследует от интерфейса Close able, определенного в пакете j ava. io. Оба интерфейса реализуются классами потоков, в том числе FilelnputStream и FileOutputStream. Следовательно, оператор try с ресурсами может применяться вместе с потоками, включая и потоки файлового ввода-вывода.
В качестве примера ниже приведена переделанная версия программы ShowFile, в которой оператор try с ресурсами применяется для автоматического закрытия файла.

/* В этой версии программы ShowFile оператор try с ресурсами применяется для автоматического закрытия файла, когда он уже больше не нужен. Примечание: для компиляции этого кода требуется JDK 7 или более поздняя версия данного комплекта. / import java.io.;

class ShowFile {

public static void main(String args[]) {

int i;

// Прежде всего следует убедиться, что оба файла были указаны,

if(args.length != 1) {

System.out.println("Usage: ShowFile filename");

return; }

// Ниже оператор try с ресурсами применяется сначала для открытия, а

// затем для автоматического закрытия файла после выхода из блока try.

try(FilelnputStream fin = new FilelnputStream(args[0])) {

// Блок оператора try с ресурсами,

do {

i = fin.read();

if (i != -1) System.out.print((char) i) ;

} while(i != -1);
} catch(IOException exc) {

System.out.println("I/O Error: " + exc);

}

} }

Особое внимание в данной программе обращает на себя следующая строка кода, в которой файл открывается в операторе try с ресурсами.

try(FilelnputStream fin = new FilelnputStream(args[0])) {

Как видите, в той части оператора try с ресурсами, где указывается конкретный ресурс, объявляется переменная fin типа FilelnputStream, которой затем присваивается ссылка на файл как объект, открываемый конструктором класса FilelnputStream. Следовательно, в данной версии программы переменная fin является локальной для блока try и создается при входе в этот блок. А при выходе из блока try файл, связанный с переменной fin, автоматически закрывается с помощью неявно вызываемого метода close(). Это означает, что метод close() не нужно вызывать явным образом, а следовательно, он избавляет от необходимости помнить, что файл нужно закрыть. Именно в этом и заключается главное преимущество автоматического управления ресурсами.
Следует иметь в виду, что ресурс, объявляемый в операторе try с ресурсами, неявно считается как final. Это означает, что ресурс нельзя присвоить после того, как он был создан. Кроме того, область действия ресурса ограничивается блоком оператора try с ресурсами.
С помощью одного оператора try с ресурсами можно управлять несколькими ресурсами. Для этого достаточно указать каждый из них через точку с запятой. В качестве примера ниже приведена переделанная версия рассмотренной ранее программы CopyFile. В этой версии оператор с ресурсами используется для управления переменными fin и fout, ссылающимися на два ресурса (в данном случае — оригинал и копию файла).

/* В этой версии программы CopyFile используется оператор try с ресурсами. В ней демонстрируется управление двумя ресурсами (в данном случае — файлами) с помощью единственного оператора try.

Примечание: для компиляции этого кода требуется JDK 7 или более поздняя версия данного комплекта. / import java.io.;

class CopyFile {

public static void main.(String args[] ) throws IOException {

int i;

// Прежде всего следует убедиться, что оба файла были указаны,

if(args.length != 2) {

System.out.println("Usage: CopyFile from to");

return; }

// открыть оба файла для управления с помощью оператора try

try (FilelnputStream fin = new FilelnputStream(args[0]);

FileOutputStream fout = new FileOutputStream(args[1]))

// Управление двумя ресурсами (в данном случае — файлами).

{

do {

i = fin.read();

if(i != -1) fout.write(i);

} whiled ! = -1) ;

} catch(IOException exc) {

System.out.println("I/O Error: " + exc);

}

} } Обратите внимание на то, каким образом входной и выходной файлы открываются в операторе try с ресурсами, как показано ниже.

try (FilelnputStream fin = new FilelnputStream(args[0]);

FileOutputStream fout = new FileOutputStream(args[1]))

{

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

Следует также упомянуть о еще одной особенности оператора try с ресурсами. Вообще говоря, когда выполняется блок try, в нем может возникнуть одно исключение, приводящее к другому исключению при закрытии ресурса в блоке finally. И если это блок обычного оператора try, то исходное исключение теряется, прерываясь вторым исключением. А в блоке оператора try с ресурсами второе исключение подавляется. Но оно не теряется, а добавляется в список подавленных исключений, связанных с первым исключением. Этот список можно получить, вызвав метод get Suppressed(), определенный в классе Throwable.

В силу упомянутых выше преимуществ, присущих оператору try с ресурсами, можно ожидать, что он найдет широкое применение в программировании на Java. Поэтому именно он и будет использоваться в остальных примерах программ, представленных далее в этой главе. Но не менее важным остается и умение пользоваться рассмотренным ранее традиционным способом освобождения ресурсов с помощью вызываемого явным образом оператора close(). И на то имеется ряд веских оснований. Во-первых, уже существует немало написанных и повсеместно эксплуатируемых программ на Java, в которых применяется традиционный способ управления ресурсами. Поэтому все программирующие на Java должны как следует усвоить и уметь пользоваться этим традиционным способом для сопровождения устаревшего кода. Во-вторых, переход на JDK 7 может произойти не сразу, а следовательно, придется работать с предыдущей версией данного комплекта. В этом случае воспользоваться преимуществами оператора try с ресурсами не удастся и придется применять традиционный способ управления ресурсами. И наконец, в некоторых классах закрытие ресурса явным образом может оказаться более пригодным, чем его автоматическое освобождение. Но, несмотря на все сказанное выше, новый способ автоматического управления ресурсами считается более предпочтительным при переходе к JDK 7 или более поздней версии данного комплекта, поскольку он рациональнее и надежнее традиционного способа.

1   ...   5   6   7   8   9   10   11   12   ...   39


написать администратору сайта