上一篇博客讲了IO流中的字节流,分别介绍输入字节流和输出字节流的类结构图; 主要介绍字节流的读取方式和输出方式,分表对应着read()方法和write()方法;然后介绍不同形式的来源有不同的读取方式,同时根据输出的目的地不同有不同的输出方式。具体的内容可以看这里,这里我们介绍IO流中的字符流。
1 输入字符流
输入字符流的父类是抽象类Reader,它除了实现Closeable接口,还需要实现Readable接口;
Abstract class for reading character streams. The only methods that a subclass must implement are read(char[], int, int) and close(). Most subclasses, however, will override some of the methods defined here in order to provide higher efficiency, additional functionality, or both.
用于读取字符流的抽象类。子类必须实现的惟一方法是read(char[],int,int)和close()。但是,大多数子类将覆盖这里定义的一些方法,以便提供更高的效率、额外的功能。
public abstract class Reader implements Readable, Closeable {
protected Object lock;
protected Reader() {
this.lock = this;
}
protected Reader(Object lock) {
if (lock == null) {
throw new NullPointerException();
}
this.lock = lock;
}
public int read(java.nio.CharBuffer target) throws IOException {
int len = target.remaining();
char[] cbuf = new char[len];
int n = read(cbuf, 0, len);
if (n > 0)
target.put(cbuf, 0, n);
return n;
}
public int read() throws IOException {
char cb[] = new char[1];
if (read(cb, 0, 1) == -1)
return -1;
else
return cb[0];
}
public int read(char cbuf[]) throws IOException {
return read(cbuf, 0, cbuf.length);
}
abstract public int read(char cbuf[], int off, int len) throws IOException;
abstract public void close() throws IOException;
// ……
}
上面是输入字符流的主要方法,有必要弄懂每个方法的含义。
-
有一个Object类型的名为lock对象,意则表明对输入字符流对象的操作是同步的,即阻塞IO。两个构造方法表明可以通过约束自身或者外部对象达到对该字符流对象的操作是同步的。
-
read(CharBuffer target)方法: 它是接口Readable中唯一一个方法,下面看一下接口Readable的解释
A Readable is a source of characters. Characters from a Readable are made available to callers of the read method via a java.nio.CharBuffer CharBuffer.
Readable是字符的来源。Readable中的字符通过CharBuffer提供给read方法的调用者.
观察该方法的实现可以知道读取后存储的形式是字符数组,读取方法是抽象方法read(char[],int,int),并且会把读取的内容放入CharBuffer对象中。
-
read()方法: 是一个重载方法,用于读取单个字符;此方法将阻塞,直到字符可用、发生I/O错误或到达流的末尾。
-
read(char[])方法:是一个重载的方法;将字符读入字符数组。此方法将阻塞,直到某些输入可用、发生I/O错误或到达流的末尾。
-
read(char[],int,int)方法:一个重载的方法;将字符读入特定长度的字符数组内。此方法将阻塞,直到某些输入可用、发生I/O错误或到达流的末尾。它是一个抽象方法,需要子类实现。
-
close()方法:关闭流并且释放与之相关的系统资源。不同的读取方式关闭方式不一样,因此需要子类实现。
输入字符流的类图:
类图中的主要类
StringReader:该类用于读取字符串中的字符。
实现read(char[],int,int)方法:
public int read(char cbuf[], int off, int len) throws IOException {
synchronized (lock) {
ensureOpen();
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
if (next >= length)
return -1;
int n = Math.min(length - next, len);
str.getChars(next, next + n, cbuf, off);
next += n;
return n;
}
}
上面的实现表明:
- 方法为同步方法;
- 调用的是String类中的getChars()方法读取字符串中的字符;
- 方法返回读取的字符个数;如果读取到流的末尾则返回-1;
实现close()方法:
public void close() {
str = null;
}
方法直接把字符串置空,等待GC。
另外,还重写了read()方法:
public int read() throws IOException {
synchronized (lock) {
ensureOpen();
if (next >= length)
return -1;
return str.charAt(next++);
}
}
该方法也是同步方法,直接返回当前字符的int形式.
该输入字符流的实例
static void stringReader() throws IOException {
String test = "hello word";
StringReader srReader = new StringReader(test);
char[] charRead = new char[test.length()];
int firstChar;
while ((firstChar = srReader.read()) > 0) {
charRead[0] = (char)firstChar;
int num = srReader.read(charRead, 1, test.length()-1);
System.out.println(num); // 9
System.out.println(new String(charRead)); // hello word
}
srReader.read();
}
方法需要注意read()方法返回test字符串中的第一个字符,所以需要手动把它写入charRead字符数组。
InputStreamReader
InputStreamReader是一个从字节流到字符流的桥梁,它读取字节并使用指定的字符集将其解码为字符。它使用的字符集可以通过名称指定,也可以显式给出,或者可以接受平台的默认字符集。
对于不方便用字符读取的内容,可以通过字节流的形式读取然后转换成字符流,我们常用的FileReader就是使用这种方式。
read()方法
public int read() throws IOException {
return sd.read();
}
实则调用StreamDecoder类的read()方法。
read(char[],int,int)
public int read(char cbuf[], int offset, int length) throws IOException {
return sd.read(cbuf, offset, length);
}
实则调用StreamDecoder类的read(char,int,int)方法。
Close()方法
public void close() throws IOException {
sd.close();
}
实则调用StreamDecoder类的close()方法.
所以StreamDecoder类非常重要。关于StreamDecoder类可以看这篇博客,我后续也会进一步研究。
该输入字符流的实例
static void inputStreamReader() throws IOException {
InputStreamReader isReader = new InputStreamReader(new FileInputStream("file/fis.txt"));
char[] charReader = new char[100];
while(isReader.read() > 0) {
isReader.read(charReader);
System.out.println(new String(charReader));
System.out.println();
}
}
每次读取100个字符。
每次调用InputStreamReader的read()方法都会导致从底层字节输入流读取一个或多个字节。为了有效地将字节转换为字符,可以从底层流提前读取比满足当前读取操作所需的更多的字节。为了提高效率,可以考虑将InputStreamReader包装在BufferedReader中。
BufferedReader
从字符输入流中读取文本,缓冲字符以便有效读取字符、数组和行。所以,可以认为BufferedReader是个包装类,把各种输入字符流包装成缓冲字符流,目的就是提高读取的效率。
BufferedReader默认一次性读取8192个字符,也就是16KB,这个默认大小满足大多数的场景。
该类的实例:
static void bufferedReader() throws IOException {
BufferedReader bReader = new BufferedReader(new InputStreamReader(new FileInputStream("file/fis.txt")));
do {
System.out.println(bReader.readLine());
}while(bReader.readLine() != null);
}
整行读取,效率大大提高。
2 输出字符流
输出字节流的父类是抽象类Write,它需要实现三个接口:Closeable,Flushable和Appendable;它的功能是:
Abstract class for writing to character streams. The only methods that a subclass must implement are write(char[], int, int), flush(), and close(). Most subclasses, however, will override some of the methods defined here in order to provide higher efficiency, additional functionality, or both.
用于写入字符流的抽象类。子类必须实现的惟一方法是write(char[]、int、int)、flush()和close()。但是,大多数子类将覆盖这里定义的一些方法,以便提供更高的效率、额外的功能。
或许你注意到第一次出现的接口Appendable,它的主要作用是把有效的unicode字符追加到字符序列上。表现在其内部方法append(charSequence)!
抽象类Write中重要的方法有:
public abstract class Writer implements Appendable, Closeable, Flushable {
abstract public void write(char cbuf[], int off, int len) throws IOException;
abstract public void flush() throws IOException;
abstract public void close() throws IOException;
}
- write(char[],int,int)方法根据输出的目的地不同而不同;
- flush()方法的作用是这样解释的:
Flushes the stream. If the stream has saved any characters from the various write() methods in a buffer, write them immediately to their intended destination. Then, if that destination is another character or byte stream, flush it. Thus one flush() invocation will flush all the buffers in a chain of Writers and OutputStreams.
刷新流。如果流已将各个write()方法中的任何字符保存在缓冲区中,则立即将它们写到预期的目的地。然后,如果目标是另一个字符或字节流,则刷新它。因此,一次flush()调用将刷新写入器和输出流链中的所有缓冲区。
If the intended destination of this stream is an abstraction provided by the underlying operating system, for example a file, then flushing the stream guarantees only that bytes previously written to the stream are passed to the operating system for writing; it does not guarantee that they are actually written to a physical device such as a disk drive.
如果这个流的目标是底层操作系统提供的抽象,例如一个文件,那么刷新流只保证先前写入流的字节被传递到操作系统进行写入;它不能保证它们实际上被写入物理设备,比如磁盘驱动器。
- Close()方法就是关闭与之相关的资源。
输出字符流的类图:
输出字符流跟输入字符流是反向的,所以这里就不过多的介绍,下面给出几个主要输出字符流的使用实例:
- StringWriter类:一种字符流,将其输出收集到字符串缓冲区中,然后可用于构造字符串。
static void stringWriter() {
String test = "hello world";
StringWriter sWriter = new StringWriter();
sWriter.write(test);
System.out.println(sWriter.toString()); // hello world
}
- OutputStreamWriter:是从字符流到字节流的桥梁:写入它的字符使用指定的字符集编码为字节。它使用的字符集可以通过名称指定,也可以显式给出,或者可以接受平台的默认字符集。
static void outputStreamWriter () throws IOException {
OutputStreamWriter osWriter = new OutputStreamWriter(new FileOutputStream("file/osWriter.txt"));
String test = "hello world";
osWriter.write(test);
osWriter.close(); // 记得关闭流
}
把“hello world”写入文件file/osWriter.txt。
- BufferedWriter类:将文本写入字符输出流,缓冲字符以便有效地写入单个字符、数组和字符串。
static void bufferWriter() throws IOException {
BufferedWriter bWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("file/bWriter.txt")));
String test = "test test test test";
bWriter.write(test);
bWriter.close();
}
3 总结
本篇主要介绍输入字节流和输出字节流,要特别注意InputStreamReader和OutputStreamWriter,两者可以实现字节流到字符流的互转。
源码地址查看github项目