20.3 Character Streams
读写字符流的抽象类是Reader和Writer。它们所支持的方法都和相对的InputStream和OutputStream所支持的方法相类似。比如,InputStream有一个read方法,读取一个int变量的低8位;Reader也有一个read方法,读取一个int变量的低16位。OutputStream支持向字节数组中写入,Writer也有写入char数组的方法。字符流是在字节流之后被设计,用以提供对Unicode码的全方位支持,在改进过程中使其更易于使用。
图20-2描述了java.io库中的字符流的继承体系。
就像字节流一样,字符流应该显示地关闭来释放与流关联的资源。20.5.1讲述字符流的同步机制。
20.3.1 Reader
抽象类Reader提供了一个字符流,类似字节流InputStream。Reader的方法基本上和InputStream相同:
public int read() throws IOException
读取一个字符,并返回一个介于0到65535之间的整数。如果没有字符可用,比如到流尾部,返回-1。这个方法将被堵塞直到有可用字符输入,或者到达流尾,或者异常发生。
public abstract int read(char[] buf,int offset,int count)throws IOException
读取到一个字符数组中。最大读入字符数是count。读入的字符从buf[offset]开始放,一直到buf[offset+count-1], buf中的其它值均不变。函数返回实际读入的字符数。如果因为到达流尾没有字符读入,返回-1。如果count为0,那么没有字符被读入,返回0。这个方法将会被阻塞,直到又可读的字符,或者到达流尾,或者异常发生。如果不是因为到达流尾,造成第一个字符都无法读入,比如流已经被关闭,那么会抛出IOException。一旦读入字符,之后任何读取失败都不会造成异常,但是函数会把读取失败视为到达流尾正常返回,并返回实际读取的字符数。
public int read(char[] buf) throws IOException
等价于read(buf, 0, buf.length)。
public int read(java.nio.CharBuffer buf[]) throws IOException
尝试读取尽可能多的字符到指定的字符缓冲中,而不撑爆它。实际读取的字符数将被返回。如果因为到达流尾没有字符可读取,就返回-1。这个方法等价于将字符读取到一个和缓冲区大小相当的一个数组中,然后将数组中的元素拷贝到缓冲区中。这个方法在接口
java.lang.Readable接口中定义,但是InputStream中没有相应的方法。
public long skip(long count) throws IOException
跳过输入流中至多count个字符直到流尾。返回实际跳过的字符数。count不能为负。
public boolean ready() throws IOException
如果流是可以读取的返回true。也就是说至少有一个字符可以读取。注意返回false并不意味着调用read将会被阻塞,因为在调用read的时候数据可能已经可以读取了。
public abstract void close() throws IOException
关闭流。这个方法应该被调用来释放任何和流关联的资源(比如,文件句柄)。一旦一个流被关闭,对这个流的后继操作将会导致IOException被抛出。关闭一个已经关闭的流没有任何效果。
Reader的实现需要子类实现读取到一个字符数组的read方法和close方法。许多子类为了提升性能也重写了其它一些方法。
Reader和InputStream之间有很多不同。Reader所有读操作都以读取到一个字符数组为基础,而InputStream将以读取单个字节为基础。Reader的子类必须实现close方法,而字节流中缺省是空实现。最后,InputStream有个available方法告诉还有多少个字节可以读取,而Reader则简单提供了ready方法告诉是否有数据可以读取。
下面这个例子将统计一个字符流的所有空白字符。
import java.io.*;
class CountSpace{
public static void main(String[] args)
throws IOException
{
Reader in;
if(args.length==0)
in = new InputStreamReader(System.in);
else
in = new FileReader(args[0]);
int ch;
int total;
int spaces = 0;
for(total = 0; (ch = in.read())!=-1;total++){
if(Character.isWhitespace((char)ch))
spaces++;
}
System.out.println(total + "chars,"
+ spaces + "spaces");
}
}
程序从命令行中得到文件名。变量in代表字符流。如果没有输入文件名,标准输入流System.in将被封装成InputStreamReader,由它将输入的字节流转成输入字符流。如果输入文件名,FileReader实例被创建,这是一个Reader的子类。
for循环统计文件中的字符数和空白字符数。Character的isWhitespace方法用来测试一个字符是否是空白字符。
20.3.2 Writer
类似于OutputStream处理字节流,抽象类Writer用来处理字符流。Writer提供的方法和OutputStream提供的方法大致相同,不过加入了其它有用的write方法。
public void write(int ch) throws IOException
将ch以一个字符写入。如果将字符以int类型传入,只是低16bit被写入。这个方法将被阻塞直到字符被写入。
public abstract void write(char[] buf, int offset, int count) throws IOException
写入字符数组中的一部分,从buf[offset]开始,直到写入count个字符。这个方法将被阻塞直到字符被写入。
public void write(char[] buf)throws IOException
等价于write(buf,0,buf.length)
public void write(String str, int offset, int count)throws IOException
从str中str.charAt(offset)开始写入count个字符。
public void write(String str) throws IOException
等价于write(str, 0, str.length())
public abstract void flush() throws IOException
清洗字符流。如果字符流缓冲了write方法的任何字符,flush调用就会马上执行写入。如果写入对象是另外一个流,这个流也将会被清洗。一旦flush被调用,流串中的所有缓冲都将会被清洗。如果这个流没有缓冲机制,flush什么也不做。
public abstract void close() throws IOException
关闭字符流,如果必要还要清洗。这个方法应该被调用用来释放任何资源(比如文件句柄)。一旦字符流被关闭,后继的任何操作都会抛出异常。关闭一个已经关闭的流没有任何效果。
Writer的子类必须实现数组写入write方法,close方法和flush方法。Writer的其他方法的实现都依赖于这三个方法。而OutputStream将单字节写入write方法为基础方法,此外它还为flush和close方法提供了默认实现。和Reader一样,子类如果为了提升性能可以重写其它方法。
Writer同时还继承了java.lang.Appendable接口。append(char c)方法就等同于write(c); 接收CharSequence参数的append方法等同于write(String str)方法。
20.3.3字符流和标准流
标准流System.in, System.out和System.err在字符流发布之前就存在了,所以这些流都是字节流,尽管它们应该是字符流。这样就会造成许多麻烦。比如,这样就不可能用LineNumberReader来替换System.in,从而没有办法记录标准输入的当前行数。但是通过
InputStreamReader, 可以将一个字节流转换成一个字符流,这样System.in就可以使用LineNumberReader来记录输入的当前行数。但是System.in是一个InputStream,所以你不可能将其替换成LineNumberReader,这是一个Reader,而不是一个InputStream。System.out和System.err都是PrintStream对象。PrintStream已经被其基于字符的版本PrintWriter代替。总之你应该避免创建一个PrintStream对象。