Cодержание
- Введение
- Разница между бинарным потоком и символьным потоком
- Обзор символьных потоков
- Class java.io.Reader
- Class java.io.Writer
- Как конвертировать бинарный поток в символьный поток?
- Class java.io.BufferedReader
- Class java.io.BufferedWriter
- Class java.io.FilterReader
- Class java.io.FilterWriter
- Class java.util.PushbackReader
- Class java.io.PrintWriter
- Class java.io.CharArrayReader
- Class java.io.CharArrayWriter
- Class java.io.PipedReader
- Class java.io.PipedWriter
Руководство Java IO Character Streams
View more Tutorials:
- Введение
- Разница между бинарным потоком и символьным потоком
- Обзор символьных потоков
- Class java.io.Reader
- Class java.io.Writer
- Как конвертировать бинарный поток в символьный поток?
- Class java.io.BufferedReader
- Class java.io.BufferedWriter
- Class java.io.FilterReader
- Class java.io.FilterWriter
- Class java.util.PushbackReader
- Class java.io.PrintWriter
- Class java.io.CharArrayReader
- Class java.io.CharArrayWriter
- Class java.io.PipedReader
- Class java.io.PipedWriter
В предыдущем руководстве я ознакомил вас с входным-выходным потоком (input-output binary stream), вам нужно понять о нем перед тем как изучать про входной-выходной символьный поток (input-output character stream), вы можете посмотреть по ссылке:
Бинарный поток (binary stream), каждый раз читает/записывает 1 byte (Равный 8 bit)

При этом, символьный поток (character stream) каждый раз читает/записывает один символ, в зависимости от кодирования (encoding) (UTF-8, UTF-16,..) где этот символ будет равен 1, 2 или 3 byte. Посмотрим на иллюстрированное изображение ниже:
UTF-16:
Это текст на японском, если сохраниться в файле с кодированием UTF-16 (UTF-16 encoding), то byte в жестком диске будут похожи как в изображении ниже:
- Два первых byte (254,255) только означают оповещение о начале новой строки с кодом UTF-16.
- Следующие символы будут кодированы с помощью 2 byte.
- Например символ 'J' кодирован с помощью 2 byte (0 и 74)
- Символ 'P' кодирован с помощью 2 byte (0 и 80)
- ....
- При чтении из файла по кодированию UTF-16, он пропустит первые 2 byte, и прочитает последовательные 2 byte соединенные в один символ.

UTF-8:
Тот же текст на японском выше при записи в файл с кодированием UTF-8 будет другим, посмотрим byte сохраненные на жестком диске:
- С символами ASCII, используется только 1 byte для хранения.
- Например, он использует 1 byte для хранения символа 'J' (74).
- Используется 1 byte для хранения символа 'P' (80).
- Для хранения других символов может использовать 2 byte или 3 byte.
- Правило для чтения 1 символа, основываясь на таблице кода UTF-8 (UTF-8 Table).
- Читает первый byte, если <= 127, то это 1 символ ASCII.
- Напротив если > 127, то нужно прочитать дальше второй byte, и проверить можно эти 2 byte соединить в 1 символ основывая на таблице кода UTF-8 или нет.
- Если первые 2 byte не соответствуют одному символу, он читает дальше третий byte и соединяет в 1 символ.
- UTF-8 использует максимум 3 byte для хранения одного символа.

То есть когда вы сохраняете текст с любым кодированием (encoding), нужно прочитаеть с соответсвующим кодированием, не то чтение принесет неправильный результат.
Иерархия классов:

Reader являет абстрактным классом, это базовый класс для сивольных потоков чтения.
Создать файл test_reader.txt чтобы начать пример с Reader:

HelloReader.java
package org.o7planning.tutorial.javaio.readerwriter; import java.io.FileReader; import java.io.IOException; import java.io.Reader; public class HelloReader { public static void main(String[] args) throws IOException { // Создать Reader (Символьный поток), чтобы прочитать файл. // С кодированием (encoding) по умолчанию. Reader r = new FileReader("test_reader.txt"); int i = -1; // Прочитать поочередно символы в потоке. while ((i = r.read()) != -1) { // Сделать cast в вид символа. System.out.println((char) i); } r.close(); } }
Результат запуска примера:

Следующий пример прочитает много символов за один раз. Этот способ увеличивает эффективность программы по сравнению с поочередным чтением каждого символа.
HelloReader2.java
package org.o7planning.tutorial.javaio.readerwriter; import java.io.FileReader; import java.io.IOException; import java.io.Reader; // Данный пример прочитает много символов за один раз. public class HelloReader2 { public static void main(String[] args) throws IOException { // Создать объект Reader для чтения файла. // С кодированием (encoding) по умолчанию. Reader r = new FileReader("test_reader.txt"); // Создать временным массив. char[] temp = new char[10]; int i = -1; // Метод read(char[]): // Прочитает много символов за один раз, и прикрепляет к элементам массива. // Возвращает количество прочитанных символов. // Возвращает -1 если поток (stream) закончится. while ((i = r.read(temp)) != -1) { String s = new String(temp, 0, i); System.out.println(s); } r.close(); } }
Writer это абстрактный класс, являющийся базой символьного потока для записи.
HelloWriter.java
package org.o7planning.tutorial.javaio.readerwriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; public class HelloWriter { public static void main(String[] args) throws IOException { File dir = new File("C:/test"); // Создать папку 'C:/test', если она еще не существует. dir.mkdirs(); // Создать объект Writer, для записи данных в файл. // С кодированием (encoding) по умолчанию. Writer w = new FileWriter("C:/test/test_writer.txt"); // Массив символов. char[] chars = new char[] { 'H', 'e', 'l', 'l', 'o', // ' ', 'w', 'r', 'i', 't', 'e', 'r' }; // Поочередная запись символов в поток (stream). for (int i = 0; i < chars.length; i++) { char ch = chars[i]; int j = (int) ch; // w.write(j); } // Закрыть поток (Close stream), w.close(); } }
Результаты запуска примера:

Следующий пример это записать много символов в поток за один раз. Точнее записать массив символов в поток. Это повышает эффективность программы по сравнению с поочередной записью каждого символа.
HelloWriter2.java
package org.o7planning.tutorial.javaio.readerwriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; public class HelloWriter2 { public static void main(String[] args) throws IOException { File dir = new File("C:/test"); // Создать папку 'C:/test' если она еще не существует. dir.mkdirs(); // Создать объект Writer, для записи данных в файл. Writer w = new FileWriter("C:/test/test_writer2.txt"); // char[] chars = new char[] { 'H', 'e', 'l', 'l', 'o', // ' ', 'w', 'r', 'i', 't', 'e', 'r' }; // Записать все символы в массиве в поток. w.write(chars); // Обычно Java использует буфер (buffer) // чтобы временно сохранитьданные. // Когда буфер (buffer) заполнен, данные будут сдвинуты (flush) в файл. // Вы так же можете сами сдвинуть (flush) данные в файл. w.flush(); // Записать символ с новой строки (new line character) в поток. w.write('\n'); String s = "FileWriter"; // Записать строку в поток (stream). w.write(s); // Закрыть поток (Close stream). // Он сдвинет данные с буфера (buffer) в файл. // Одновременно закончит запись данных. w.close(); } }
Результаты запуска примера:

У вас есть бинарный поток (binary stream). И вы хотите конвертировать его в символьный поток (character stream)?

В примерах выше мы ознакомились с Reader, Writer. Следующий пример позволяет вам прочитать и написать данные в поток с опредленным кодом.
Создать файл test_utf8.txt

test_utf8.txt
JP日本-八洲
При Save, Eclipse спросит вас с каким кодированием вы хотите сохранить, выберите UTF-8.

InputStreamReaderExample.java
package org.o7planning.tutorial.javaio; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; public class InputStreamReaderExample { public static void main(String[] args) throws IOException { // Создать binary Stream (бинарный поток), для чтения файла. InputStream in = new FileInputStream("test_utf8.txt"); // Создать Character stream (символьный поток) обертывающий (wrap) бинарный поток выше. // Với mã hóa (encoding) là UTF-8. Reader reader = new InputStreamReader(in, "UTF-8"); int i = 0; // Прочитать поочередно каждый символ. while ((i = reader.read()) != -1) { // Сделать cast в символ и напечатать на экране. System.out.println((char) i + " " + i); } reader.close(); } }
Результаты запуска примера

Следующий пример записывает файл с определенным кодированием UTF-8.
OutputStreamWriterExample.java
package org.o7planning.tutorial.javaio; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; public class OutputStreamWriterExample { public static void main(String[] args) throws IOException { File dir = new File("C:/test"); // Создать папку 'C:/test' если она не существует. dir.mkdirs(); // Создать OutputStream (выходной поток) для записи данных в файл. OutputStream out = new FileOutputStream("C:/test/test_write_utf8.txt"); // Создать Character Stream (символьный поток) обертывающий OutputStream выше. // Кодированием (encoding) является UTF-8. Writer writer = new OutputStreamWriter(out, "UTF-8"); String s = "JP日本-八洲"; writer.write(s); writer.close(); } }
Результаты запуска примера

Если вы хотите прочитать данные текстового файла. BufferedReader является хорошим выбором.

// BufferedReader является прямым подклассом Reader . // Constructor: public BufferedReader(Reader in); // Удобный метод полученный от BufferedReader. // Чтение строки текста. public String readLine();
Ví dụ:
// Пример 1: Reader r=new FileReader("C:/test.txt"); BufferedReader br=new BufferedReader(r); // Пример 2: InputStream in = new FileInputStream("C:/test.txt"); Reader r = new InputStreamReader(in, "UTF-8"); BufferReader br = new BufferedReader(r);

test_multi_lines.txt
## Fruit List Apricots Barbados Cherries Bitter Melon Cherimoya Honeydew Jackfruit Limes Lychee Mango Oranges Pineapple Strawberries
BufferedReaderExample.java
package org.o7planning.tutorial.javaio.buffered; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; public class BufferedReaderExample { public static void main(String[] args) throws IOException { InputStream in = new FileInputStream("test_multi_lines.txt"); Reader reader = new InputStreamReader(in, "UTF-8"); BufferedReader br = new BufferedReader(reader); String s = null; int i = 0; // Прочитать каждую строку (line) данных. // Если читается null значит завершился Stream. while ((s = br.readLine()) != null) { i++; System.out.println(i + " : " + s); } br.close(); } }
Результаты запуска примера:

BufferedWriter это прямой подкласс Writer.
// Создать объект BufferedWriter // с помощью обертывания (wrap) другого объекта Writer. public BufferedWriter(Writer out); // Индентично с write('\n'); public String newLine();
Пример:
// Создать объект Writer. Writer w=new FileWriter("C:/test/test_bufferedWriter.txt"); // Создать объект BufferedWriter обертывающий (wrap) writer. BufferedWriter bw=new BufferedWriter(w); bw.write("Hello.."); // Записать символ с новой строки '\n'. bw.newLine();
FilterReader это подкласс Reader. Он выборочно читает символы по запросу. Например вы хотите прочитать текст HTML, и пропустить символы в теге (tag). Вам нужно написать подкласс FilterReader и потом использовать его, вы не можете использовать FilterReader напрямую так как он является абстрактным классом (abstract class).

Например, создайте подкласс класса FilterReader, чтобы прочитать данные HTML, но пропустить символы в теге.
Пример входа "<h1>Hello</h1>" ==> выход "Hello".
Пример входа "<h1>Hello</h1>" ==> выход "Hello".
RemoveHTMLReader.java
package org.o7planning.tutorial.javaio.filter; import java.io.FilterReader; import java.io.IOException; import java.io.Reader; public class RemoveHTMLReader extends FilterReader { boolean intag = false; public RemoveHTMLReader(Reader in) { super(in); } // Мы переопределим (override) данный метод. // Правилом будет: // Прочитать только символы находящиеся вне тегов (tag). @Override public int read(char[] buf, int from, int len) throws IOException { int charCount = 0; while (charCount == 0) { charCount = super.read(buf, from, len); if (charCount == -1) { // Завершение stream. return -1; } int last = from; for (int i = from; i < from + charCount; i++) { // Если не находится в теге HTML. if (!intag) { if (buf[i] == '<') { intag = true; } else { buf[last++] = buf[i]; } } else if (buf[i] == '>') { intag = false; } } charCount = last - from; } return charCount; } // Тоже нужно переопределить данный метод. @Override public int read() throws IOException { char[] buf = new char[1]; int result = read(buf, 0, 1); if (result == -1) { return -1; } else { return (int) buf[0]; } } }
RemoveHTMLReaderTest.java
package org.o7planning.tutorial.javaio.filter; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; public class RemoveHTMLReaderTest { public static void main(String[] args) throws IOException { // Создать объект Reader из Constructor StringReader. Reader in = new StringReader("<h1>Hello \n <b>World</b><h1>"); RemoveHTMLReader filterReader = new RemoveHTMLReader(in); BufferedReader br = new BufferedReader(filterReader); String s = null; while ((s = br.readLine()) != null) { System.out.println(s); } br.close(); } }
Результаты запуска примера:

FilterWriter это прямой подкласс Writer, он записывает выборочно символы по запросу. Вам нужно написать подкласс FilterWriter и переопределить (override) некоторые методы класса FilterWriter.

Пример: Символы меняются при записи в поток.
Rot13.java
package org.o7planning.tutorial.javaio.filter; public class Rot13 { /** * <pre> * a ==> n * b ==> o * c ==> p * d ==> q * e ==> r * ... * y ==> l * z ==> m * </pre> */ public static int rotate(int inChar) { int outChar; if (inChar >= (int) 'a' && inChar <= (int) 'z') { outChar = (((inChar - 'a') + 13) % 26) + 'a'; } else if (inChar >= (int) 'A' && inChar <= (int) 'Z') { outChar = (((inChar - 'A') + 13) % 26) + 'A'; } else { outChar = inChar; } return outChar; } // Test public static void main(String[] args) { for(char ch='a'; ch<='z';ch++ ) { char m= (char)rotate(ch); System.out.println("ch="+ch+" ==> "+ m); } } }
RotateWriter.java
package org.o7planning.tutorial.javaio.filter; import java.io.FilterWriter; import java.io.IOException; import java.io.Writer; public class RotateWriter extends FilterWriter { public RotateWriter(Writer out) { super(out); } // Переопределить один или более методов, чтобы обработать фильтрование. // (Переопределить оба метода будет безопаснее). @Override public void write(int outChar) throws IOException { super.write(Rot13.rotate(outChar)); } @Override public void write(char[] cbuf, int offset, int length) throws IOException { char[] tempbuf = new char[length]; for (int i = 0; i < length; i++) { tempbuf[i] = (char) Rot13.rotate(cbuf[offset + i]); } super.write(tempbuf, 0, length); } }
RotateWriterTest.java
package org.o7planning.tutorial.javaio.filter; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; public class RotateWriterTest { public static void main(String[] args) throws IOException { String s="abcdef"; Writer writer= new StringWriter(); RotateWriter rw= new RotateWriter(writer); rw.write(s.toCharArray(),0,s.length()); rw.close(); String rotateString = writer.toString(); System.out.println("rotateString="+ rotateString); } }
Результаты запуска примера:

Класс PushbackReader позволяет вернуть один или много символов (push back) для следующего потока после их чтения. Ниэе являются два его Constructor:

public PushbackReader(Reader inputStream) public PushbackReader(Reader inputStream, int bufSize)
Некоторые дополнительные методы:
// Сдвигает назад (push back) символ в поток. public void unread(int c) throws IOException
Пример:
PushbackReaderDemo.java
package org.o7planning.tutorial.javaio.pushback; import java.io.CharArrayReader; import java.io.IOException; import java.io.PushbackReader; class PushbackReaderDemo { public static void main(String args[]) throws IOException { String s = "if (a == 4) a = 0;\\n"; char buf[] = new char[s.length()]; s.getChars(0, s.length(), buf, 0); CharArrayReader in = new CharArrayReader(buf); PushbackReader f = new PushbackReader(in); int c; while ((c = f.read()) != -1) { switch (c) { // Найти символ '=' case '=': // Прочитать следующий символ. // (После нахождения символа '='). if ((c = f.read()) == '=') { System.out.print(".eq."); } else { System.out.print("<-"); // Сдвинуть назад (Pushes back) этот символ в поток. // Похоже на перемещение курсора обратно на позицию. f.unread(c); } break; default: System.out.print((char) c); break; } } } }
Результаты запуска примера:


// Constructor: // Обернуть (wrap) объект Writer. public PrintWriter(Writer out) public PrintWriter(Writer out,boolean autoFlush) // Обернуть (wrap) объект OutputStream. public PrintWriter(OutputStream out) public PrintWriter(OutputStream out,boolean autoFlush) public PrintWriter(String fileName) ... // Method: public void println(String s) public void print(char ch)
StackTraceToFile.java
package org.o7planning.tutorial.javaio.printwriter; import java.io.File; import java.io.FileWriter; import java.io.PrintWriter; import java.io.Writer; public class StackTraceToFile { public static void main(String[] args) { try { // Сделать что-то. // Ошибка деления на 0. int i = 10 / 0; } catch (Exception e) { System.out.println("EXCEPTION ...."); try { File dir = new File("C:/test"); // Создать папку если она не существует. dir.mkdirs(); // Создать Stream для записи данных в файл. Writer w = new FileWriter("C:/test/stackTrace.txt"); // Создать объект PrintWriter обертывающий объект Writer 'w'. // Таким образом, данные записанные в PrintWriter будут записаны в FileWriter 'w'. PrintWriter pw = new PrintWriter(w); // Записать ошибочную информацию в поток 'pw'. e.printStackTrace(pw); System.out.println("Finish !"); } catch (Exception e1) { System.out.println("Error:" + e); } } } }
StackTraceToString.java
package org.o7planning.tutorial.javaio.printwriter; import java.io.PrintWriter; import java.io.StringWriter; public class StackTraceToString { public static void main(String[] args) { try { // Сделать что-то // Ошибка деления на 0. int i = 1000 / 0; } catch (Exception e) { System.out.println("EXCEPTION ...."); try { StringWriter sw = new StringWriter(); // Создать объект PrintWriter обертывающий StringWriter 'sw'. // Таким образом, данные записанные в PrintWriter будут записаны в 'sw'. PrintWriter pw = new PrintWriter(sw); // Записать ошибочную информацию в 'pw'. e.printStackTrace(pw); StringBuffer sb = sw.getBuffer(); String s = sb.toString(); System.out.println("Exception String:"); System.out.println(s); } catch (Exception e1) { System.out.println("Error:" + e); } } } }
Результаты запуска примера:


CharArrayReaderDemo.java
package org.o7planning.tutorial.javaio.chararray; import java.io.CharArrayReader; import java.io.IOException; public class CharArrayReaderDemo { public static void main(String args[]) throws IOException { String tmp = "abcdefghijklmnopqrstuvwxyz"; int length = tmp.length(); char c[] = new char[length]; tmp.getChars(0, length, c, 0); CharArrayReader input1 = new CharArrayReader(c); CharArrayReader input2 = new CharArrayReader(c, 0, 5); int i; System.out.println("input1 is:"); while ((i = input1.read()) != -1) { System.out.print((char) i); } System.out.println(); System.out.println("input2 is:"); while ((i = input2.read()) != -1) { System.out.print((char) i); } System.out.println(); } }
Результаты запуска примера:


Некоторые дополнительные методы:
// Записывает данные этого поток в другой поток. public void writeTo(Writer out) throws IOException
CharArrayWriterDemo.java
package org.o7planning.tutorial.javaio.chararray; import java.io.CharArrayWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; public class CharArrayWriterDemo { public static void main(String args[]) throws IOException { char[] c = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!' }; CharArrayWriter out = new CharArrayWriter(); // Записать данные в 'out' out.write(c); File dir = new File("C:/test"); dir.mkdirs(); FileWriter f1 = new FileWriter(new File("C:/test/a.txt")); // Записать данные 'out' в 'f1'. out.writeTo(f1); FileWriter f2 = new FileWriter(new File("C:/test/b.txt")); // Записать данные 'out' в 'f2'. out.writeTo(f2); f1.close(); f2.close(); // Закрыть поток CharArrayWriter 'out'. out.close(); FileWriter f3 = new FileWriter(new File("C:/test/c.txt")); // С CharArrayWriter, после закрытия. // Метод writeTo(..) больше не работает. // И не создает исключение если вы используете writeTo(..). out.writeTo(f3); System.out.println("Done!"); } }

- TODO
PipeReaderExample1.java
package org.o7planning.tutorial.javaio.pipereader; import java.io.IOException; import java.io.Reader; import java.io.PipedReader; import java.io.PipedWriter; public class PipeReaderExample1 { private Reader pipedReader; public static void main(String[] args) throws IOException, InterruptedException { new PipeReaderExample1().test(); } private void test() throws IOException, InterruptedException { // Create a 'pipedWriter', PipedWriter pipedWriter = new PipedWriter(); // Data writing to 'pipedWriter' // will automatically appear in 'pipedReader'. pipedReader = new PipedReader(pipedWriter); new ThreadRead().start(); char[] chs = new char[] { 'a', 'a', 'b', 'c' , 'e' }; // Write data to 'pipedWriter'. for (char ch : chs) { pipedWriter.write(ch); Thread.sleep(1000); } pipedWriter.close(); } // A Thread to read the data that appears on 'pipedReader'. class ThreadRead extends Thread { @Override public void run() { try { int data = 0; while ((data = pipedReader.read()) != -1) { System.out.println((char) data); } } catch (Exception e) { e.printStackTrace(); } finally { closeQuietly(pipedReader); } } } private void closeQuietly(Reader is) { if (is != null) { try { is.close(); } catch (IOException e) { } } } }

PipeReaderExample2.java
package org.o7planning.tutorial.javaio.pipereader; import java.io.BufferedReader; import java.io.IOException; import java.io.PipedReader; import java.io.PipedWriter; import java.io.Reader; public class PipeReaderExample2 { private BufferedReader bufferedReader; public static void main(String[] args) throws IOException, InterruptedException { new PipeReaderExample2().test(); } private void test() throws IOException, InterruptedException { // Create a 'pipedWriter', PipedWriter pipedWriter = new PipedWriter(); // Data writing to 'pipedWriter' // will automatically appear in 'pipedReader'. PipedReader pipedReader = new PipedReader(pipedWriter); // Tạo một 'bufferedReader' wrapped 'pipedReader'. bufferedReader = new BufferedReader(pipedReader); new ThreadRead().start(); String[] strs = new String[] { "Hello ", "There", "\n", "I am ", "Tran" }; // Write data to 'pipedWriter'. for (String str : strs) { pipedWriter.write(str); Thread.sleep(500); } pipedWriter.close(); } // A Thread to read the data that appears on 'bufferedReader' ('pipedReader'). class ThreadRead extends Thread { @Override public void run() { try { String line = null; while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } } catch (Exception e) { e.printStackTrace(); } finally { closeQuietly(bufferedReader); } } } private void closeQuietly(Reader reader) { if (reader != null) { try { reader.close(); } catch (IOException e) { } } } }

