上一节我们讲了文件流。当我们读取数据时遇到了一些问题,就是我们写入文件的是一个个的字符,但是我们读出来的却是一个个的字节。后来我们通过String类的带byte数组类型的构造方法,将其转换成了字符。那我们要问了,有没有一种办法,让我读出来的就是字符,而不需要我自己手动转呢?还真有!输入输出流的家族非常的庞大,我并不想在这里展示它的家族体系,因为没有意义。我想通过一篇讲一种流的方式,最后再总结它们的家族体系,这样思路会清晰的多。所以,这一篇我们讲字符流。
输入流
Java提供了Reader作为字符的输入流。它也在jdk的java.io
包下,我们先来看看它是如何定义的:
public abstract class Reader implements Readable, Closeable {}
这是Reader的类定义。和InputStream一样,它也是抽象的。我们不可以直接用它,必须得创建一个子类继承它,实现它定义的抽象方法才行。我们来看看它都有哪些抽象的方法:
// 从cbuf的off索引处开始,读取len个字符到cbuf,返回读取到的字符数
abstract public int read(char cbuf[], int off, int len) throws IOException;
// 关闭输入流
abstract public void close() throws IOException;
read方法和InputStream的read方法差不多,不同的是InputStream读取的是字节,而Reader读取的是字符。close方法是用来关闭输入流的。我不知道大家对字节和字符的概念是不是清晰的,我大概的讲一下吧。字节,标准的byte,读取的数是一个数字,而且范围在-128到127之间;而字符不一定,它因码表而异。例如我们常见的ASCII码表,它每个字符占1个字节。而我们中国使用的是GBK编码(国标扩展码)。GBK编码中,一个中文汉字占2个字节,也就是说,如果我们传输过来的是2个字节,按照ASCII表来解码的话,可以得到两个拉丁字符;而如果按照GBK表来解码的话,只能得到一个中文汉字。这里就是关于字节和字符的知识补充了。
我们再看看它都提供了哪些非抽象方法吧!
// 将字符读取到CharBuffer里,返回读取到的字符数
public int read(java.nio.CharBuffer target) throws IOException {}
// 读取一个字符,返回该字符在码表的索引值
public int read() throws IOException {}
// 将数据读取到cbuf数组,返回读取到的字符数
public int read(char cbuf[]) throws IOException {}
// 跳过n个字符
public long skip(long n) throws IOException {}
// 为输入流的当前位置打一个标记
public void mark(int readAheadLimit) throws IOException {}
// 重置索引到标记位置
public void reset() throws IOException {}
这些方法和InputStream中的都差不多,无非就是字符和字节的区别,这里就不再赘述。
输出流
同样的,有进就有出。Java提供了Writer类供我们对字符进行写操作。我们看看Writer是如何定义的吧!
public abstract class Writer implements Appendable, Closeable, Flushable {}
它也是个抽象类。看看它都有哪些抽象方法!
// 将cbuf中的数据从off开始,写入len个字符
abstract public void write(char cbuf[], int off, int len) throws IOException;
// 将流数据写入目的地
abstract public void flush() throws IOException;
// 关闭输出流
abstract public void close() throws IOException;
还用解释吗?完全不需要!只要是吸收了前两节中讲的内容,这些东西完全能理解。再来看看他都提供了哪些非抽象方法!
// 写入码表下,c索引处的字符
public void write(int c) throws IOException {}
// 写入字符数组
public void write(char cbuf[]) throws IOException {}
// 写入字符串
public void write(String str) throws IOException {}
// 从字符串的off索引处开始,写入len个字符
public void write(String str, int off, int len) throws IOException {}
// 写入字符序列,返回当前对象
public Writer append(CharSequence csq) throws IOException {}
// 从字符序列的off索引处开始,写入len个字符,返回当前对象
public Writer append(CharSequence csq, int start, int end) throws IOException {}
// 写入一个字符,返回当前对象
public Writer append(char c) throws IOException {
前面4个方法我们都很熟悉,和OutputStream差不多,不同的是这里写入字符。后面三个方法写入的是字符序列对象,其返回的是当前对象,它的意思是支持我们链式调用。另外,如果对象为空,就会写入null
这个字符串。
这两个流,对于我们来说理解起来完全没有压力,因为有了前两节的基础。现在,我们学到的IO流家族体系应该是这样的!