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

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


Скачать 1.93 Mb.
НазваниеJava io. Ключевым понятием здесь является понятие потока
Дата03.07.2022
Размер1.93 Mb.
Формат файлаdoc
Имя файлаОтветы на вопросы по ревью 4.doc
ТипДокументы
#623608
страница35 из 39
1   ...   31   32   33   34   35   36   37   38   39

Сам по себе: RandomAccessFile


RandomAccessFile используется для файлов, содержащих записи известного размера, так что вы можете переместиться от одной записи к другой, используя seek( ), затем прочесть или изменить запись. Записи могут и не быть одинакового размера; вы просто способны определить их размер и их положение в файле.

Сначала немного трудно поверить, что RandomAccessFile не является частью иерархии InputStream или OutputStream. Однако он не имеет ассоциаций с этими иерархиями, за исключением того, что он реализует интерфейсы DataInput и DataOutput (которые так же реализуются DataInputStream и DataOutputStream). Он даже не использует любую функциональность существующих классов InputStream или OutputStream — это полностью отдельный класс, написанный для поиска, имеющий все свои собственные (в большинстве своем родные) методы. Объяснением этого может быть то, что RandomAccessFile имеет во многом отличающееся поведение по сравнению с остальными типами ввода/вывода, так как вы можете перемещаться вперед и назад в пределах файла. В любом случае, он стоит отдельно, как прямой потомок от Object.

По существу, RandomAccessFile работает как DataInputStream совмещенный с DataOutputStream, благодаря использованию методов getFilePointer( ) для нахождения местоположения в файле, seek( ) для перемещения в новую точку в файле и length( ) для определения максимального размера файла. Кроме того, конструктор требует второй аргумент (что идентично fopen( ) в C), указывающий будите ли вы производить только чтение в произвольном порядке (“r”) или чтение и запись (“rw”). Нет поддержки для файлов только для чтения, что может сказать о том, что RandomAccessFile мог бы хорошо работать, если он наследовался бы от DataInputStream.

Метод поиска есть только у RandomAccessFile, который работает только с файлами. BufferedInputStream позволяет вам выполнять маркировку позиции с помощью метода mark( ) (чье значение содержится в единственной внутренней переменной) и сброс этой позиции методом reset( ), но это ограничено и не очень полезно.

Типичное использование потоков ввода/вывода


Хотя вы можете комбинировать классы потоков ввода/вывода многими различными способами, вы, вероятно, будете использовать несколько комбинаций. Следующий пример может быть использован как отправная точка; он показывает создание и использование типичной конфигурации ввода/вывода. Обратите внимание, что каждая конфигурация начинается с порядкового номера и заголовка, который оглавляет соответствующее объяснение в следующем за ним тексте.

//: c11:IOStreamDemo.java

// Типичные конфигурации потоков ввода/вывода.

import java.io.*;
public class IOStreamDemo {

// Выбрасывание исключения на консоль:

public static void main(String[] args)

throws IOException {

// 1. Чтение ввода по строкам:

BufferedReader in =

new BufferedReader(

new FileReader("IOStreamDemo.java"));

String s, s2 = new String();

while((s = in.readLine())!= null)

s2 += s + "\n";

in.close();
// 1b. Чтение стандартного ввода:

BufferedReader stdin =

new BufferedReader(

new InputStreamReader(System.in));

System.out.print("Enter a line:");

System.out.println(stdin.readLine());
// 2. Ввод из памяти

StringReader in2 = new StringReader(s2);

int c;

while((c = in2.read()) != -1)

System.out.print((char)c);
// 3. Форматированный ввод из памяти

try {

DataInputStream in3 =

new DataInputStream(

new ByteArrayInputStream(s2.getBytes()));

while(true)

System.out.print((char)in3.readByte());

} catch(EOFException e) {

System.err.println("End of stream");

}
// 4. Вывод в файл

try {

BufferedReader in4 =

new BufferedReader(

new StringReader(s2));

PrintWriter out1 =

new PrintWriter(

new BufferedWriter(

new FileWriter("IODemo.out")));

int lineCount = 1;

while((s = in4.readLine()) != null )

out1.println(lineCount++ + ": " + s);

out1.close();

} catch(EOFException e) {

System.err.println("End of stream");

}
// 5. Хранение и перекрытие данных

try {

DataOutputStream out2 =

new DataOutputStream(

new BufferedOutputStream(

new FileOutputStream("Data.txt")));

out2.writeDouble(3.14159);

out2.writeChars("That was pi\n");

out2.writeBytes("That was pi\n");

out2.close();

DataInputStream in5 =

new DataInputStream(

new BufferedInputStream(

new FileInputStream("Data.txt")));

BufferedReader in5br =

new BufferedReader(

new InputStreamReader(in5));

// Необходимо использовать DataInputStream для данных:

System.out.println(in5.readDouble());

// Теперь можно использовать "правильный" readLine():

System.out.println(in5br.readLine());

// Но выводимая строка забавна.

// Строка, созданная с помощью writeBytes, в порядке:

System.out.println(in5br.readLine());

} catch(EOFException e) {

System.err.println("End of stream");

}
// 6. Чтение/запись файлов в произвольном порядке

RandomAccessFile rf =

new RandomAccessFile("rtest.dat", "rw");

for(int i = 0; i < 10; i++)

rf.writeDouble(i*1.414);

rf.close();
rf =

new RandomAccessFile("rtest.dat", "rw");

rf.seek(5*8);

rf.writeDouble(47.0001);

rf.close();
rf =

new RandomAccessFile("rtest.dat", "r");

for(int i = 0; i < 10; i++)

System.out.println(

"Value " + i + ": " +

rf.readDouble());

rf.close();

}

} ///:

Здесь приведено описание для нумерованных разделов программы:

Потоки ввода


Части с 1 по 4 демонстрируют создание и использование потоков ввода. Часть 4 также показывает простое использование потока вывода.

1. Буферизированный ввод из файла


Для открытия файла для ввода символов вы используете FileInputReader с объектом String или File в качестве имени файла. Для быстрой работы вы можете захотеть, чтобы файл был буферизированный, поэтому вы передаете результирующую ссылку в конструктор BufferedReaderBufferedReader также обеспечивает метод readLine( ), так что это ваш конечный объект и интерфейс, из которого вы читаете. Когда вы достигаете конца файла, readLine( ) возвращает null, что используется для окончания цикла while.

String s2 использует для аккумулирования всего содержимого файла (включая символы новой строки, которые должны добавляться, поскольку readLine( ) отбрасывает их). s2 далее используется в следующих частях этой программы. В конце вызывается close( ) для закрытия файла. Технически, close( ) будет вызвано при запуске finalize( ), а это произойдет (не зависимо от того произойдет или нет сборка мусора) при выходе из программы. Однако это было реализовано неустойчиво, поэтому безопасным подходом является явный вызов close( ) для файлов.

Раздел 1b показывает, как вы можете использовать System.in для чтения консольного ввода. System.in является DataInputStream и для BufferedReader необходим аргумент Reader, так что InputStreamReader вовлекается для выполнения перевода.

2. Ввод из памяти


Эта секция берет String s2, которая теперь включает все содержимое файла, и использует его для создания StringReader. Затем используется read( ) для чтения каждого символа, один символ за обращение, который посылается на консоль. Обратите, что read( ) возвращает следующий байт как int, поэтому он должен быть приведен к типу char для правильной печати.

3. Форматированный ввод из памяти


Для чтения “форматированных” данных вы используете DataInputStream, который является байт-ориентированным классом ввода/вывода (а не символьно-ориентированным). Таким образом, вы должны использовать все классы InputStream, а не классы Reader. Конечно, вы можете читать все, что угодно (также как и файл) байтами, используя классы InputStream, но здесь используется String. Для преобразования String в массив байт, который является подходящим для ByteArrayInputStreamString имеет метод getBytes( ), чтобы сделать эту работу. В этой точке вы имеете соответствующий InputStream для управления DataInputStream.

Если вы читаете символы из DataInputStream по одному байту, используя readByte( ), любое байтовое значение является допустимым результатом, так что возвращаемое значение не может использоваться для обнаружения конца ввода. Вместо этого вы можете использовать метод available( ) для нахождения как много символов доступно. Вот пример, который показывает, как читать файл по одному байту:

//: c11:TestEOF.java

// Проверка на конец файла

// при чтении по одному байту.

import java.io.*;
public class TestEOF {

// Выбрасывается исключение на консоль:

public static void main(String[] args)

throws IOException {

DataInputStream in =

new DataInputStream(

new BufferedInputStream(

new FileInputStream("TestEof.java")));

while(in.available() != 0)

System.out.print((char)in.readByte());

}

} ///:

Обратите внимание, что available( ) работает по разному в зависимости от сорта носителя, из которого вы читаете; буквально - “это число байт, которые могут быть прочитаны без блокировки”. Для файлов это означает весь файл, но для другого вида потоков это может не быть правдой, так что используйте его осторожно.

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

4. Вывод в файл


Этот пример также показывает, как писать данные в файл. Сначала создается FileWriter для соединения с файлом. Фактически, вы всегда будете буферизировать вывод, обернув его с помощью BufferedWriter (попробуйте удалить эту обертку, чтобы посмотреть влияние на производительность — буферизация позволяет значительно увеличить производительность операций ввода/вывода). Затем, для форматирование объект включен в PrintWriter. Файл данных, созданный этим способом, читаем, как обычный текстовый файл.

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

Когда входной поток исчерпан, readLine( ) возвращает null. Вы увидите явный вызов close( ) для out1, в противном случае, если вы не вызовите close( ) для всех своих выходных файлов, вы можете обнаружить, что данные из буферов не вытолкнуты, поэтому файлы не завершенные.

Выходные потоки


Два первичных вида потоков вывода делятся по способу записи данных: одни пишут их для потребления людей, а другие пишут данные для повторного использования с DataInputStreamRandomAccessFile стоит в стороне, хотя его формат данных совместим с DataInputStream и DataOutputStream.

5. Сохранение и возврат


PrintWriter форматирует данные так, чтобы их читали люди. Однако для вывода данных в виде, чтобы они могли быть возвращены в другой поток, используйте DataOutputStream для записи данных, а DataInputStream для обратного получения данных. Конечно, эти потоки могут быть всем, что угодно, но здесь используется файл, буферизируемый и для чтения, и для записи. DataOutputStream и DataInputStream являются байт-ориентированными и поэтому требуют потоков InputStream и OutputStream.

Если вы используете DataOutputStream для записи данных, то Java гарантирует, что вы можете безошибочно повторно задействовать данные, используя DataInputStream — не зависимо от различий платформ для записи и чтения данных. Это невероятно ценно, как могут подтвердить те, кто потратил время, заботясь о платформозависимых путях движения данными. Эти проблемы снимаются, если вы имеете Java на обеих платформах. [58]

Обратите внимание, что строки символов записываются с использованием как writeChars( ), так и writeBytes( ). Когда вы запустите программу, вы обнаружите, что выводит 16-битные символы Unicode. Когда вы читаете строки, используя readLine( ), вы увидите, что есть пространство между символами, потому что каждый дополнительный байт вставляется из-за Unicode. Так как нет дополнительного метода “readChars” для DataInputStream, вы вынуждены вытягивать символы по одному с помощью readChar( ). Так что для ASCII легче написать символы байтами, за которым следует новая строка, а затем использовать readLine( ) для чтения байтов, как обычной ASCII cтроки.

writeDouble( ) сохраняет числа типа double в потоке, а дополнительный метод readDouble( ) получает их обратно (есть аналогичные методы для чтения и записи остальных типов). Но для корректной работы с любым читающим методом вы должны знать точное положение элемента данных в потоке, чтобы было одинаково возможно читать хранимое double, как простую последовательность байт, или как char, и т.п. Таким образом, вы должны либо иметь фиксированный формат для данных в файле, или в файле должна хранится дополнительная информация, которую вы обработаете для определения местоположения данных.

6. Чтение и запись файлов произвольного доступа


Как было замечено ранее, RandomAccessFile почти полностью изолирован от оставшейся иерархии ввода/вывода, и подтвержден тот факт, что он реализует интерфейсы DataInput и DataOutput. Поэтому вы не можете комбинировать его с любыми другими аспектами подклассов InputStream и OutputStream. Даже при том, что имело бы смысл трактовать ByteArrayInputStream, как элемент произвольного доступа, вы можете использовать RandomAccessFile только для открытия файла. Вы должны иметь в виду, что RandomAccessFile буферизирован должным образом, так что вам не нужно заботится об этом.

Одну из настроек вы имеете во втором конструкторе аргумента: вы можете открыть RandomAccessFile для чтения (“r”) или для чтения и записи (“rw”).

Использование RandomAccessFile аналогично использования комбинации DataInputStream и DataOutputStream (потому что он реализует эквивалентные интерфейсы). Кроме того, вы можете видеть, что seek( ) используется для перемещения в файле и изменения одного значения на другое.

Ошибка?


Если вы взглянете на раздел 5, вы увидите, что данные записываются перед текстом. Дело в том, что эта проблема была представлена в Java 1.1 (и сохранилась в Java 2), я был уверен, что это ошибка. Когда я сообщил об этом людям, занимающимся ошибками в JavaSoft, они сказали мне, что это, Проблема показана в следующем коде:

//: c11:IOProblem.java

// Java 1.1 и высшая проблема ввода/вывода.

import java.io.*;
public class IOProblem {

// Исключение выбрасывается на консоль:

public static void main(String[] args)

throws IOException {

DataOutputStream out =

new DataOutputStream(

new BufferedOutputStream(

new FileOutputStream("Data.txt")));

out.writeDouble(3.14159);

out.writeBytes("That was the value of pi\n");

out.writeBytes("This is pi/2:\n");

out.writeDouble(3.14159/2);

out.close();
DataInputStream in =

new DataInputStream(

new BufferedInputStream(

new FileInputStream("Data.txt")));

BufferedReader inbr =

new BufferedReader(

new InputStreamReader(in));

// Double, записанное ПЕРЕД текстом

// считывается правильно:

System.out.println(in.readDouble());

// Читаем строки текста:

System.out.println(inbr.readLine());

System.out.println(inbr.readLine());

// Попытка читать double после строки

// производит исключение конца файла:

System.out.println(in.readDouble());

}

} ///:

Кажется что все, что вы пишите после вызова writeBytes( ) не возвращаемо. Ответ, очевидно, тот же, что и в случае старой шутки водителя: “Доктор, мне больно, когда я делаю это!” “Так не делайте этого!”

Потоки в виде трубопровода


PipedInputStreamPipedOutputStreamPipedReader и PipedWriter будут упомянуты только вскользь в этой главе. Это не означает, что они бесполезны, но их значение не будет очевидно, пока вы не поймете многонитевые процессы, так как потоки в виде трубопровода используются для общения между нитями. Это будет освещено в примере Главы 14.
1   ...   31   32   33   34   35   36   37   38   39


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