Java-программы выполняют ввод/вывод через потоки. Поток является абстракцией, которая или производит, или потребляет информацию. Поток связывается с физическим устройством с помощью системы ввода/вывода Java (Java I/O system). Все потоки ведут себя одинаковым образом, хотя фактические физические устройства, с которыми они связаны, могут сильно различаться. Таким образом, одни и те же классы и методы ввода/вывода можно применять к устройствам любого типа. Это означает, что поток ввода может извлекать много различных видов входных данных: из дискового файла, с клавиатуры или сетевого разъема. Аналогично, поток вывода может обратиться к консоли, дисковому файлу или сетевому соединению (сокету).
Благодаря потокам, ваша программа выполняет ввод/вывод, не понимая различий между клавиатурой и сетью. Java реализует потоки с помощью иерархии классов, определенных в пакете java.io.
Байтовые и символьные потоки
Java 2 определяет два типа потоков: байтовый и символьный. Байтовые потоки предоставляют удобные средства для обработки ввода и вывода байт. Байтовые потоки используются, например, при чтении или записи данных в двоичном коде. Символьные потоки предоставляют удобные средства для обработки ввода и вывода символов. Они используют Unicode и поэтому могут быть. Кроме того, в некоторых случаях символьные потоки более эффективны, чем байтовые. Первоначальная версия Java (Java 1.0) не включала символьные потоки, и таким образом, весь ввод/вывод был байтовым. Символьные потоки были добавлены в Java 1.1, а некоторые байтовые классы и методы были исключены.
Классы байтовых потоков
Байтовые потоки определяются в двух иерархиях классов. Наверху этой иерархии — два абстрактных класса: InputStream и OutputStream. Каждый из этих абстрактных классов имеет несколько конкретных подклассов , которые обрабатывают различия между разными устройствами, такими, как дисковые файлы, сетевые соединения и даже буферы памяти.
Абстрактные классы InputStream и OutputStream определяют несколько ключевых методов, которые реализуются другими поточными классами. Два наиболее важных— read() и write(), которые соответственно читают и записывают байты данных. Оба метода объявлены как абстрактные внутри
классов InputStream и OutputStream и переопределяются производными поточными классами.
| Поточный класс | Назначение |
| InputStream OutputStream |
Абстрактные классы, которые описывают поточный ввод и вывод |
| BufferedInputStream BufferedOutputStream |
Буферизированный поток ввода и вывода |
| ByteArrayOutputStream | Поток ввода, который читает из байт-массива Поток вывода, который записывает в байт-массив |
| FileInputStream FileOutputStream |
Поток ввода, который читает из файла Поток вывода, который записывает в файл |
| RandomAccessFile | Поддерживает ввод/вывод файла произвольного доступа |
Классы символьных потоков
Символьные потоки определены в двух иерархиях классов. Наверху этой иерархии два абстрактных класса: Reader и Writer. Они обрабатывают потоки символов Unicode. В Java существуют несколько конкретных подклассов каждого из них. Классы Reader и Writer – наследники InputStream и OutputStream. Если с их помощью записывать или считывать текст, то сначала необходимо сопоставить каждому символу его числовой код. Такое соответствие называется кодировкой. Классы символьных потоков показаны в таблице.
Абстрактные классы Reader и Writer определяют несколько ключевых методов, которые реализуются другими поточными классами. Два самых важных метода — read() и write(), которые читают и записывают символы данных, соответственно. Они переопределяются производными поточными классами.
| Символьный класс | Назначение |
| Reader Writer |
Абстрактные классы символьного потока ввода и вывода |
| BufferedReader BufferedWriter |
Буферизированные символьные потоки ввода и вывода |
| FileReader FileWriter |
Поток ввода, который читает поток символов из файла; Выходной поток, который записывает символы в файл |
| StringReader StringWriter |
Поток ввода, который читает из строки; Поток вывода, который записывает в строку |
| PrintWriter | Поток вывода, который поддерживает методы print() и println() |
Известно, что Java использует кодировку Unicode, в которой символы представляются двухбайтным кодом. Байтовые потоки зачастую работают с текстом упрощенно – они просто отбрасывают старший байт каждого символа. В реальных же приложениях они могут использовать различные кодировки (даже для русского языка их существует несколько). Поэтому в версии Java 1.1 появился дополнительный набор классов, основывающийся на типах Reader и Writer.
Эта иерархия очень схожа с аналогичной для байтовых потоков InputStream и OutputStream. Главное отличие между ними – Reader и Writer работают с потоком символов (char). Только чтение массива символов в Reader описывается методом read(char[]), а запись в Writer – write(char[]).
В таблице приведены соответствия классов для байтовых и символьных потоков.
| Байтовый поток | Символьный поток |
| InputStream | Reader |
| OutputStream | Writer |
| ByteArrayInputStream | CharArrayReader |
| ByteArrayOutputStream | CharArrayWriter |
| Нет аналога | InputStreamReader |
| Нет аналога | OutputStreamWriter |
| FileInputStream | FileReader |
| FileOutputStream | FileWriter |
| FilterInputStream | FilterReader |
| FilterOutputStream | FilterWriter |
| BufferedInputStream | BufferedReader |
| BufferedOutputStream | BufferedWriter |
| PrintStream | PrintWriter |
| DataInputStream | Нет аналога |
| DataOutputStrea | Нет аналога |
| ObjectInputStream | Нет аналога |
| ObjectOutputStream | Нет аналога |
| PipedInputStream | PipedReader |
| PipedOutputStream | PipedWriter |
| StringBufferInputStream | StringReader |
| Нет аналога | StringWriter |
| LineNumberInputStream | LineNumberReader |
| PushBackInputStream | PushBackReader |
| SequenceInputStream | Нет аналога |
Как видно из таблицы, различия крайне незначительны и предсказуемы.
Например, конечно же, отсутствует преобразование в символьное представление примитивных типов Java и объектов (DataInput/Output, ObjectInput/Output). Добавлены классы-мосты, преобразующие символьные потоки в байтовые: InputStreamReader и OutputStreamWriter. Именно на их основе реализованы FileReader и FileWriter. Метод available() класса InputStream в классе Reader отсутствует, он заменен методом ready(), возвращающим булевое значение, – готов ли поток к считыванию (то есть будет ли считывание произведено без блокирования).
В остальном же использование символьных потоков идентично работе с байтовыми потоками.
Классы-мосты InputStreamReader и OutputStreamWriter при преобразовании символов также используют некоторую кодировку. Ее можно задать, передав в конструктор в качестве аргумента ее название. Если оно не будет соответствовать никакой из известных кодировок, будет брошено исключение UnsupportedEncodingException. Вот некоторые из корректных значений этого аргумента (чувствительного к регистру!) для распространенных кодировок: "Cp1251", "UTF-8", "8859_1" и т.д.
В следуюшем примере приведена программа, которая демонстрирует read(), читая символы с консоли, пока пользователь не напечатает «q»:
import java.io.*;
class BRRead {
public static void main(String args [ ]) throws IOException
{
char c;
BufferedReader br = new
BufferedReader(new InputStreamReader(System.in));
System.out.println("Enter symbols or 'q' to exit");
// чтение символов
do {
c= (char)br.read();
System.out.println(c);
}
while(c != 'q');
}
}
Результат выполнения программы:
Enter symbols or 'q' to exit
123abcq
1
2
3
а
b
с
q
Следующая программа демонстрирует BufferedReader и метод readLine(). Она читает и отображает строки текста, пока не будет введено слово «stop».
import java.io.*;
class BRReadLines {
public static void main(String args[])throws IOException
{
// создать BufferedReader, используя System.in
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
String str;
System.out.println("Enter text");
System.out.println("Enter 'stop' to exit");
do {
str = br.readLine();
System.out.println(str);
} while(!str.equals("stop"));
}
}





