字符流是针对字符数据的特点进行过优化的,因而提供一些面向字符的有用特性,字符流的源或目标通常是文本文件。 Reader和Writer是java.io包中所有字符流的父类。由于它们都是抽象类,所以应使用它们的子类来创建实体对象,利用对象来处理相关的读写操作。Reader和Writer的子类又可以分为两大类:一类用来从数据源读入数据或往目的地写出数据(称为节点流),另一类对数据执行某种处理(称为处理流)。

面向字符的输入流类都是Reader的子类,其类层次结构如图10-2所示。

图10-2 Reader的类层次结构图
图10-2 Reader的类层次结构图


表 10-1 列出了 Reader 的主要子类及说明。

表 10-1 Reader 的主要子类
类名功能描述
CharArrayReader从字符数组读取的输入流
BufferedReader缓冲输入字符流
PipedReader输入管道
InputStreamReader将字节转换到字符的输入流
FilterReader过滤输入流
StringReader从字符串读取的输入流
LineNumberReader为输入数据附加行号
PushbackReader返回一个字符并把此字节放回输入流
FileReader从文件读取的输入流


Reader 所提供的方法如表 10-2 所示,可以利用这些方法来获得流内的位数据。

表 10-2 Reader 的常用方法
方法功能描述
void close()关闭输入流
void mark()标记输入流的当前位置
boolean markSupported()测试输入流是否支持 mark
int read()从输入流中读取一个字符
int read(char[] ch)从输入流中读取字符数组
int read(char[] ch, int off, int len)从输入流中读 len 长的字符到 ch 内
boolean ready()测试流是否可以读取
void reset()重定位输入流
long skip(long n)跳过流内的 n 个字符

使用 FileReader 类读取文件

FileReader 类是 Reader 子类 InputStreamReader 类的子类,因此 FileReader 类既可以使用Reader 类的方法也可以使用 InputStreamReader 类的方法来创建对象。

在使用 FileReader 类读取文件时,必须先调用 FileReader()构造方法创建 FileReader 类的对象,再调用 read()方法。FileReader 构造方法的格式为:
    public FileReader(String name);  //根据文件名创建一个可读取的输入流对象

【例 10-1】利用 FileReader 类读取纯文本文件的内容(查看源代码)。

运行结果如图 10-3 所示:


图 10-3  例 10_1 运行结果(输出内容为文件ep10_1.txt的内容)


需要注意的是,Java 把一个汉字或英文字母作为一个字符对待,回车或换行作为两个字符对待。

使用 BufferedReader 类读取文件

BufferedReader 类是用来读取缓冲区中的数据。使用时必须创建 FileReader 类对象,再以该对象为参数创建 BufferedReader 类的对象。BufferedReader 类有两个构造方法,其格式为:
    public BufferedReader(Reader in);  //创建缓冲区字符输入流
    public BufferedReader(Reader in,int size);  //创建输入流并设置缓冲区大小


【例 10-2】利用 BufferedReader 类读取纯文本文件的内容(查看源代码)。

运行结果如图 10-4 所示:


图 10-4  例 10_2 运行结果


需要注意的是,执行 read()或 write()方法时,可能由于 IO 错误,系统抛出 IOException 异常,需要将执行读写操作的语句包括在 try 块中,并通过相应的 catch 块来处理可能产生的异常。

向字符的输出流都是类 Writer 的子类,其类层次结构如图 10-5 所示。

图10-5 Writer的类层次结构图
图10-5 Writer的类层次结构图


表 10-3 列出了 Writer 的主要子类及说明。

表 10-3 Writer 的主要子类
类名功能说明
CharArrayWriter写到字符数组的输出流
BufferedWriter缓冲输出字符流
PipedWriter输出管道
OutputStreamWriter转换字符到字节的输出流
FilterWriter过滤输出流
StringWriter输出到字符串的输出流
PrintWriter包含 print()和 println()的输出流
FileWriter输出到文件的输出流


Writer 所提供的方法如表 10-4 所示。

表 10-4 Writer 的常用方法
方法功能描述
void close()关闭输出流
void flush()将缓冲区中的数据写到文件中
void writer(int c)将单一字符 c 输出到流中
void writer(String str)将字符串 str 输出到流中
void writer(char[] ch)将字符数组 ch 输出到流
void writer(char[] ch, int offset, int length)将一个数组内自 offset 起到 length 长的字符输出到流

使用 FileWriter 类写入文件

FileWriter 类是 Writer 子类 OutputStreamWriter 类的子类,因此 FileWriter 类既可以使用 Writer类的方法也可以使用 OutputStreamWriter 类的方法来创建对象。

在使用 FileWriter 类写入文件时,必须先调用 FileWriter()构造方法创建 FileWriter 类的对象,再调用 writer()方法。FileWriter 构造方法的格式为:
    public FileWriter(String name);  //根据文件名创建一个可写入的输出流对象
    public FileWriter(String name,Boolean a);  //a 为真,数据将追加在文件后面


【例 10-3】利用 FileWriter 类将 ASCⅡ字符写入到文件中(查看源代码)。

运行后程序后,打开 ep10_3.txt 文件,显示内容为:
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}

使用 BufferedWriter 类写入文件

BufferedWriter 类是用来将数据写入到缓冲区。使用时必须创建 FileWriter 类对象,再以该对象为参数创建 BufferedWriter 类的对象,最后需要用 flush()方法将缓冲区清空。BufferedWriter类有两个构造方法,其格式为:
    public BufferedWriter(Writer out);  //创建缓冲区字符输出流
    public BufferedWriter(Writer out,int size);  //创建输出流并设置缓冲区大小


【例 10-4】利用 BufferedWriter 类进行文件复制(查看源代码)。

需要注意的是,调用 out 对象的 write()方法写入数据时,不会写入回车,因此需要使用newLine()方法在每行数据后加入回车,以保证目标文件与源文件相一致。




节流以字节为传输单位,用来读写8位的数据,除了能够处理纯文本文件之外,还能用来处理二进制文件的数据。 InputStream类和OutputStream类是所有字节流的父类。

InputStream类

面向字节的输入流都是InputStream类的子类,其类层次结构如图10-6所示。


图10-6 InputStream的类层次结构图


表 10-5 列出了 InputStream 的主要子类及说明。

表 10-5 InputStream 的主要子类
类名功能描述
FileInputStream从文件中读取的输入流
PipedInputStream输入管道
FilterInputStream过滤输入流
ByteArrayInputStream从字节数组读取的输入流
SequenceInputStream两个或多个输入流的联合输入流,按顺序读取
ObjectInputStream对象的输入流
LineNumberInputStream为文本文件输入流附加行号
DataInputStream包含读取 Java 标准数据类型方法的输入流
BufferedInputStream缓冲输入流
PushbackInputStream返回一个字节并把此字节放回输入流


InputStream 流类中包含一套所有输入都需要的方法,可以完成最基本的从输入流读入数据的功能。表 10-6 列出了其中常用的方法及说明。

表 10-6 InputStream 的常用方法
方法功能描述
void close()关闭输入流
void mark()标记输入流的当前位置
void reset()将读取位置返回到标记处
int read()从输入流中当前位置读入一个字节的二进制数据,以此数据为低位字节,补足16位的整型量(0~255)后返回,若输入流中当前位置没有数据,则返回-1
int read(byte b[])从输入流中的当前位置连续读入多个字节保存在数组中,并返回所读取的字节数
int read(byte b[], int off, int len)从输入流中当前位置连续读len长的字节,从数组第off+1个元素位置处开始存放,并返回所读取的字节数
int available()返回输入流中可以读取的字节数
long skip(long n)略过n个字节
long skip(long n)跳过流内的n个字符
boolean markSupported()测试输入数据流是否支持标记

OutputStream类

面向字节的输出流都是OutputStream类的子类,其类层次结构如图10-7所示。

图10-7 OutputStream的类层次结构图
图10-7 OutputStream的类层次结构图


10-7列出了OutputStream的主要子类及说明。 

表10-7 OutputStream的主要子类
类名功能描述
FileOutputStream写入文件的输出流
PipedOutputStream输出管道
FilterOutputStream过滤输出流
ByteArrayOutputStream写入字节数组的输出流
ObjectOutputStream对象的输出流
DataOutputStream包含写Java标准数据类型方法的输出流
BufferedOutputStream缓冲输出流
PrintStream包含print()和println()的输出流


OutputStream流类中包含一套所有输出都需要的方法,可以完成最基本的向输出流写入数据的功能。表10-8列出了其中常用的方法及说明。 

表10-8 OutputStream的常用方法
方法功能描述
void close()关闭输出流
void flush()强制清空缓冲区并执行向外设输出数据
void write(int b)将参数b的低位字节写入到输出流
void write(byte b[])按顺序将数组b[]中的全部字节写入到输出流
void write(byte b[], int off, int len)按顺序将数组b[]中第off+1个元素开始的len个数据写入到输出流


由于InputStream和OutputStream都是抽象类,所以在程序中创建的输入流对象一般是它们某个子类的对象,通过调用对象继承的read()和write()方法就可实现对相应外设的输入输出操作。



文件输入输出流 FileInputStream 和 FileOutputStream 负责完成对本地磁盘文件的顺序输入输出操作。

【例 10-5】通过程序创建一个文件,从键盘输入字符,当遇到字符“#”时结束,在屏幕上显示该文件的所有内容(查看源代码)。

运行后在程序目录建立一个名称为 ep10_5 的文件,运行结果如图 10-8 所示:

图 10-8   例 10_5 运行结果
图 10-8  例 10_5 运行结果


FileDescriptor 是 java.io 中的一个类,该类不能实例化,其中包含三个静态成员:in、out 和err,分别对应于标准输入流、标准输出流和标准错误流,利用它们可以在标准输入输出流上建立文件输入输出流,实现键盘输入或屏幕输出操作。

【例 10-6】实现对二进制图形文件(.gif)的备份(查看源代码)。

运行后在程序目录备份了一个名称为 ep10_6_a.gif 的文件,运行结果如图 10-9 所示:


图 10-9  例 10_6 运行结果

过滤流

FilterInputStream 和 FileOutputStream 是 InputStream 和 OutputStream 的直接子类,分别实现了在数据的读、写操作的同时能对所传输的数据做指定类型或格式的转换,即可实现对二进制字节数据的理解和编码转换。

常用的两个过滤流是数据输入流 DataInputStream 和数据输出流 DataOutputStream。其构造方法为:
    DataInputStream(InputStream in);  //创建新输入流,从指定的输入流 in 读数据
    DataOutputStream(OutputStream out);  //创建新输出流,向指定的输出流 out 写数据


由于 DataInputStream 和 DataOutputStream 分别实现了 DataInput 和 DataOutput 两个接口(这两个接口规定了基本类型数据的输入输出方法)中定义的独立于具体机器的带格式的读写操作,从而实现了对不同类型数据的读写。由构造方法可以看出,输入输出流分别作为数据输入输出流的构造方法参数,即作为过滤流必须与相应的数据流相连。

DataInputStream 和 DataOutputStream 类提供了很多个针对不同类型数据的读写方法,具体内容读者可参看 Java 的帮助文档。

【例 10-7】将三个 int 型数字 100,0,-100 写入数据文件 ep10_6.dat 中(查看源代码)。

运行后在程序目录中生成数据文件 ep10_7.dat,用文本编辑器打开后发现内容为二进制的:
00 00 00 64 00 00 00 00 FF FF FF 9C。

【例 10-8】读取数据文件 ep10_6.dat 中的三个 int 型数字,求和并显示(查看源代码)。

运行结果:
三个数的和为:0

readInt 方法可以从输入输出流中读入 4 个字节并将其作为 int 型数据直接参与运算。由于已经知道文件中有 3 个数据,所以可以使用 3 个读入语句,但若只知道文件中是 int 型数据而不知道数据的个数时该怎么办呢?因为 DataInputStream 的读入操作如遇到文件结尾就会抛出 EOFException 异常,所以可将读操作放入 try 中。
try{
    while(true)
    sum+=a.readInt();
}
catch(EOFException e){
    System.out.pritnln("三个数的和为:"+sum);
    a.close();
}
EOFException 是 IOException 的子类,只有文件结束异常时才会被捕捉到,但如果没有读到文件结尾,在读取过程中出现异常就属于 IOException。

【例 10-9】从键盘输入一个整数,求该数的各位数字之和(查看源代码)。

运行结果:
请输入一个整数:26
842403082 的各位数字之和=31

需要注意的是,输入的数据 26 为变成了 842403082,原因在于输入数据不符合基本类型数据的格式,从键盘提供的数据是字符的字节码表示方式,若输入 26,只代表 2 和 6 两个字符的字节数据,而不是代表整数 26 的字节码。

若要从键盘得到整数需要先读取字符串,再利用其他方法将字符串转化为整数。

标准输入输出

System.in、System.out、System.err 这 3 个标准输入输流对象定义在 java.lang.System 包中,这 3 个对象在 Java 源程序编译时会被自动加载。

  1. 标准输入:标准输入 System.in 是 BufferedInputStream 类的对象,当程序需要从键盘上读入数据时,只需要调用 System.in 的 read()方法即可,该方法从键盘缓冲区读入一个字节的二进制数据,返回以此字节为低位字节,高位字节为 0 的整型数据。

  2. 标准输出:标准输出 System.out 是打印输出流 PrintStream 类的对象。PrintStream 类是过滤输出流类 FilterOutputStream 的一个子类,其中定义了向屏幕输出不同类型数据的方法print()和 println()。

  3. 标准错误输出:System.err 用于为用户显示错误信息,也是由 PrintStream 类派生出来的错误流。Err 流的作用是使 print()和 println()将信息输出到 err 流并显示在屏幕上,以方便用户使用和调试程序。


【例 10-10】输入一串字符显示出来,并显示 System.in 和 System.out 所属的类(查看源代码)。

运行结果如图 10-10 所示:

图 10-10  例 10_10 运行结果
图 10-10  例 10_10 运行结果


需要注意的是,输入了 3 个字符按回车后,输出的结果显示为 5 个字符。这是由于 Java 中回车被当作两个字符,一个是 ASCⅡ为 13 的回车符,一个是值为 10 的换行符。程序中 getClass()和 ToString()是 Object 类的方法,作用分别是返回当前对象所对应的类和返回当前对象的字符串表示。