Несмотря на наличие множества различных классов IO потока внутри библиотеки, которые можно объединять различными способами, на практике используются лишь несколько наиболее распространённых конфигураций. Однако важно быть внимательным при выборе правильной комбинации. В этом примере показана создание и использование типичных конфигураций IO потока, который может служить образцом для написания своего кода. Обратите внимание, что каждая конфигурация начинается с номера в виде комментария и сопровождается соответствующими пояснениями.
//: IOStreamDemo.java
// Типичные конфигурации IO потока
import java.io.*;
import com.bruceeckel.tools.*;
public class IOStreamDemo {
public static void main(String[] args) {
try {
// 1. Управляемый входной файл
DataInputStream in =
new DataInputStream(
new BufferedInputStream(
new FileInputStream(args[0])));
String s, s2 = new String();
while ((s = in.readLine()) != null)
s2 += s + "\n";
in.close();
// 2. Чтение из памяти
StringBufferInputStream in2 =
new StringBufferInputStream(s2);
int c;
while ((c = in2.read()) != -1)
System.out.print((char) c);
// 3. Отформатированное чтение из памяти
try {
DataInputStream in3 =
new DataInputStream(
new StringBufferInputStream(s2));
while (true)
System.out.print((char) in3.readByte());
} catch (EOFException e) {
System.out.println("Конец файла достигнут");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
``````markdown
// 5. Сохранение и восстановление данных
try {
DataOutputStream out2 =
new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream("Data.txt")));
out2.writeBytes(
"Здесь значение π: \n");
out2.writeDouble(3.14159);
out2.close();
DataInputStream in5 =
new DataInputStream(
new BufferedInputStream(
new FileInputStream("Data.txt")));
System.out.println(in5.readLine());
System.out.println(in5.readDouble());
} catch (EOFException e) {
System.out.println(
"Конец потока достигнут");
}
## 10.5.1 Ввод из файла
```Конечно, одной из вещей, которую мы часто хотим сделать, это выводить отформатированную информацию в консоль, но это было упрощено в главе 5 с помощью пакета `com.bruceeckel.tools`.
Части 1 до 4 демонстрируют создание и использование входных потоков (хотя часть 4 показывает простое применение выходного потока как тестового инструмента).
### Часть 1: Буферизированный входной файл
Для открытия файла для чтения используется объект `FileInputStream`, при этом имя файла передается через объект типа `String` или `File`. Для повышения скорости лучше всего использовать буферизацию файла, что приведёт к получению ссылки на результат конструктора `BufferedInputStream`. Чтобы читать данные в отформатированном виде, эту ссылку следует передать конструктору `DataInputStream`. Объект `DataInputStream` является нашим окончательным (`final`) объектом и представляет собой интерфейс для операций чтения.В данном примере используется только метод `readLine()`, однако любые методы `DataInputStream` могут быть применены. Как только достигнут конец файла, метод `readLine()` вернёт значение `null`, чтобы завершить и выйти из цикла `while`.
Строковый объект s2
используется для агрегации всего содержимого файла (включая необходимые новые строки, поскольку readLine()
удаляет эти строки). Затем s2
используется в последующих частях программы. В конце мы вызываем close()
, чтобы закрыть файл. Теоретически, close()
будет вызван при выполнении метода finalize()
. Однако, мы хотим, чтобы это произошло сразу после завершения программы (независимо от того, происходит ли сборка мусора или нет). Тем не менее, Java 1.0 имеет серьёзную ошибку, которая делает это невозможным. В Java 1.1 требуется явный вызов System.runFinalizersOnExit(true)
, чтобы гарантировать вызов finalize()
для каждого объекта системы. Однако наиболее безопасным способом является явный вызов close()
для файла.
Эта часть использует уже содержащий полное содержимое файла `String s2` и создаёт `StringBufferInputStream` (поток ввода с буфером строк) — как параметр конструктора, требуя использовать `String`, а не `StringBuffer`. Затем мы используем `read()` для последовательного чтения каждого символа и выводим его в консоль. Обратите внимание, что `read()` возвращает следующий байт как `int`, поэтому его следует преобразовать в `char`, чтобы правильно вывести.
(3) Форматированное чтение из памяти
Интерфейс `StringBufferInputStream` ограничен, поэтому обычно его следует обернуть в `DataInputStream`, чтобы расширить его возможности. Однако если выбрать чтение одного символа за раз с помощью `readByte()`, то все значения будут корректными, поэтому нельзя больше использовать возвращаемое значение для определения окончания ввода. Вместо этого можно использовать метод `available()`, чтобы проверить количество доступных символов. Ниже приведён пример, показывающий, как читать один символ за раз из файла:
```java
//: TestEOF.java
// Проверка конца файла во время чтения
// одного байта за раз.
import java.io.*;
public class TestEOF {
public static void main(String[] args) {
try {
DataInputStream in =
new DataInputStream(
new BufferedInputStream(
new FileInputStream("TestEof.java")));
while (in.available() != 0)
System.out.print((char) in.readByte());
} catch (IOException e) {
System.err.println("IOException");
}
}
} ///:~
```Обратите внимание, что поведение `available()` зависит от текущего типа медиа, с которого производится чтение. Литерально, это означает "количество байтов, которые могут быть прочитаны без блокировки". Для файла это означает весь файл, но для других типов потока данных это может иметь другой смысл. Поэтому следует внимательно подходить к использованию данного метода. Для того чтобы обнаружить окончание ввода в такой ситуации, можно также использовать метод, основанный на захвате исключения. Однако использование исключений для управления потоками данных может показаться излишним.
(4) Нумерация строк и вывод в файл
Этот пример демонстрирует, как `LineNumberInputStream` используется для отслеживания нумерации строк входных данных. Здесь нельзя просто объединить все конструкторы вместе, так как требуется сохранять ссылку на объект `LineNumberInputStream` (необходимо отметить, что это не является наследованием, поэтому невозможно просто преобразовать `in4` в `LineNumberInputStream`). Поэтому переменная `li` хранит ссылку на объект `LineNumberInputStream`, а затем на её основе создаётся объект `DataInputStream` для чтения данных.Этот пример также показывает, как форматированная информация записывается в файл. Сначала создается объект `FileOutputStream`, который связывается с файлом. В целях повышения эффективности он генерирует объект `BufferedOutputStream`. Это почти всегда то, что мы делаем, но важно делать это явно. Затем для форматирования информации она преобразуется в объект `PrintStream`. Таким образом созданный файл данных можно считывать как обычный текстовый файл.Один из способов определить момент завершения `DataInputStream` — это вызов метода `readLine()`. Как только больше нет строк для чтения, он вернёт значение `null`. Каждая строка будет сопровождаться номером строки, который будет выведен в файл. Этот номер строки можно получить через объект `li`.
Можно заметить явное указание метода `close()` для `out1`. Такой подход может оказаться полезным, если программа собирается снова прочитать тот же самый файл. Однако данная программа до самого конца не проверяет файл `IODemo.txt`. Как уже было отмечено ранее, если не вызвать метод `close()` для всех своих выходных файлов, буферизация может не быть завершена, что приведёт к тому, что файлы будут некомплектными.
## Метод 10.5.2 Выходные потоки
Двойной основной выходной поток типа разделён по способу записи данных: один соответствует человеческому образцу чтения, другой предназначен для последующего чтения `DataInputStream`. `RandomAccessFile` независим, но его формат данных совместим с `DataInputStream` и `DataOutputStream`.
(5) Сохранение данных и повторное получение`PrintStream` форматирует данные таким образом, чтобы они были удобны для чтения. Однако, если требуется извлечь данные для повторного использования другим потоком данных, то данные должны быть записаны с помощью `DataOutputStream` и прочитаны с помощью `DataInputStream` (чтение данных). Конечно, эти данные могут быть чем угодно, но здесь используется файл, который был предварительно закэширован для увеличения скорости чтения/записи.
Обратите внимание, что строки записываются с помощью `writeBytes()`, а не `writeChars()`. При использовании последнего метода будут записаны 16-битные символы Unicode. Так как в `DataInputStream` нет дополнительного метода `readChars`, приходится использовать `readChar()` для чтения каждого символа по отдельности. Поэтому для ASCII символов лучше всего записывать каждый символ как байт, за которым следует новая строка; затем считывать эти символы обратно как обычные ASCII строки с помощью `readLine()`.Метод `writeDouble()` сохраняет значение типа `double` в поток данных, а метод `readDouble()` восстанавливает его. Однако для того чтобы любые методы чтения работали правильно, необходимо знать точное положение данных в потоке, так как сохранённое значение `double` может быть прочитано либо как простой набор байтов, либо как `char` или другой формат. Поэтому либо необходимо использовать фиксированную структуру данных для файла, либо сохранять дополнительную информацию в файле для правильной идентификации местоположения данных.
### 10.5.3 Работа со случайным доступом к файлам
Как было указано ранее, `RandomAccessFile` практически полностью изолирован от остальной части слоя ввода/вывода, хотя он также реализует интерфейсы `DataInput` и `DataOutput`. Поэтому его нельзя связывать ни с какими частями подклассов `InputStream` и `OutputStream`. Хотя можно рассматривать `ByteArrayInputStream` как элемент случайного доступа, но файл можно открыть только с помощью `RandomAccessFile`. Нужно предполагать, что `RandomAccessFile` уже корректно закэширован, поскольку мы не можем выбирать это самостоятельно.
Выбор возможен вторым параметром конструктора: можно выбрать режим "только для чтения" (`r`) или "чтение и запись" (`rw`) для открытия файла `RandomAccessFile`.При работе с `RandomAccessFile` используется сочетание методов, аналогичное тому, которое используется при работе с `DataInputStream` и `DataOutputStream` (поскольку он реализует эквивалентные интерфейсы). Кроме того, видно использование метода `seek()`, который позволяет перемещаться по всему файлу и изменять значения.## 10.5.3 Быстрое управление файлами
Учитывая то, что многие типичные формы работы с файлами были рассмотрены ранее, вы можете задуматься, почему требуется такое количество кода — это один из недостатков подхода декоратора. В этом разделе будет показано, как создать и использовать быстрые версии типичных конфигураций для чтения и записи файлов. Эти быстрые версии помещаются в пакет `com.bruceeckel.tools` (начиная с главы Yöntem Decorator'un dezavantajlarından biri bu kod miktarının gerekliliği olabilir. Bu bölümde, dosya okuma ve yazma için tipik yapılandırmaları oluşturmak ve kullanmak için hızlı versiyonlar oluşturma yöntemleri gösterilecek. Bu hızlı versiyonlar, genellikle 5. başlıklardan itibaren `com.bruceeckel.tools` paketine yerleştirilir.
## Быстрое ввод из файла
Для создания объекта, который будет читать файл из буферизированного `DataInputStream`, можно заключить этот процесс в класс с названием `InFile`. Пример ниже:
```java
//: InFile.java
// Класс-укорачивание для открытия входного файла
package com.bruceeckel.tools;
import java.io.*;
public class InFile extends DataInputStream {
public InFile(String filename)
throws FileNotFoundException {
super(new BufferedInputStream(new FileInputStream(filename)));
}
public InFile(File file)
throws FileNotFoundException {
this(file.getPath());
}
} ///:~
```
Как видно из примера, теперь можно существенно уменьшить количество повторяющегося кода при создании объекта для чтения файла.
## Быстрый вывод форматированного файла
Аналогичным образом можно создать класс `PrintFile`, который будет записывать данные в буферизированный файл. Пример расширения пакета `com.bruceeckel.tools`:
```java
//: PrintFile.java
// Класс-укорачивание для записи данных в буферизированный файл
package com.bruceeckel.tools;
import java.io.*;
public class PrintFile extends PrintWriter {
public PrintFile(String filename) {
super(new BufferedWriter(new FileWriter(filename)));
}
public PrintFile(File file) {
this(file.getPath());
}
} ///:~
``````java
//: PrintFile.java
// Класс-укорочение для открытия выходного файла
// для записи человекочитаемых данных.
package com.bruceeckel.tools;
import java.io.*;
public class PrintFile extends PrintStream {
public PrintFile(String filename)
throws IOException {
super(
new BufferedOutputStream(
new FileOutputStream(filename)));
}
public PrintFile(File file)
throws IOException {
this(file.getPath());
}
} ///:~
```
Обратите внимание, что конструктор не может перехватить исключение, выброшенное базовым конструктором.
## Быстрый вывод данных в файл
Наконец, аналогичным образом можно создать буферизированный поток для записи данных (в отличие от форматированных данных для человека):
```java
//: OutFile.java
// Класс-укорочение для открытия выходного файла
// для хранения данных.
package com.bruceeckel.tools;
import java.io.*;
public class OutFile extends DataOutputStream {
public OutFile(String filename)
throws IOException {
super(
new BufferedOutputStream(
new FileOutputStream(filename)));
}
public OutFile(File file)
throws IOException {
this(file.getPath());
}
} ///:~
```
Очень странно и очень печально, что дизайнеры Java-библиотек не учли возможность использования таких удобных методов как часть стандартной библиотеки.## 10.5.4 Чтение данных из стандартного вводаНа основе концепций "стандартного ввода", "стандартного вывода" и "стандартного вывода ошибок", первоначально продвигаемых Unix, Java предоставляет соответствующие `System.in`, `System.out` и `System.err`. В течение всего этого учебника вы будете сталкиваться с использованием `System.out` для стандартного вывода, который уже предварительно упакован в объект типа `PrintStream`.`System.err` также является объектом типа `PrintStream`, но `System.in` представляет собой примитивный `InputStream`, не прошедший никакой обработки. Это означает, что хотя можно использовать `System.out` и `System.err` напрямую, `System.in` должен быть предварительно упакован, чтобы из него можно было читать данные.
В типичной ситуации мы хотим использовать метод `readLine()` для чтения одной строки ввода за раз, поэтому нам нужно упаковать `System.in` в объект типа `DataInputStream`. Это старый способ, используемый в Java Yöntemi 1.0 для чтения строковых данных. В конце этой главы вы узнаете решение, предлагаемое Java 1.1. Вот простой пример программы, которая отвечает каждому введенному нами строковому значению:
```java
//: Echo.java
// Как читать данные из стандартного ввода
import java.io.*;
public class Echo {
public static void main(String[] args) {
DataInputStream in =
new DataInputStream(
new BufferedInputStream(System.in));
String s;
try {
while ((s = in.readLine()).length() != 0)
System.out.println(s);
// Пустая строка завершает программу
} catch (IOException e) {
e.printStackTrace();
}
}
} ///:~
```
Почему используется блок `try`, это связано с тем, что метод `readLine()` может выбросить исключение `IOException`. Обратите внимание, что, как и большинство других потоков, `System.in` следует буферизировать.
Упаковка `System.in` в объект типа `DataInputStream` в каждом приложении делает процесс немного неудобным. Однако использование такого подхода позволяет получить максимальную гибкость.
---
Исправлено:
1. "Yöntemi 1.0" заменено на "Java 1.0".
2. Удалены лишние пробелы после некоторых слов.
3. Корректные знаки препинания.
4. Сохранена структура и разметка исходного текста.## 10.5.5 Потоки данных через каналы
В этой главе были кратко рассмотрены `PipedInputStream` (канальный входной поток) и `PipedOutputStream` (канальный выходной поток). Хотя описание не было подробным, это не значит, что эти классы не имеют значения. Однако полное понимание их ценности возможно только после освоения концепции многопоточного программирования. Причина проста — канальные потоки данных используются для связи между потоками. Пример использования этих потоков будет приведен в главе 14.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )