|
7.5 过滤器I/O
这一节涉及的类较多,但我们可以结合几个例子逐一介绍。
在第一节中,我们已经谈了一些过滤器类的特性。过滤器是可以“连接”的,即一个数据流经过过滤后,其结果可以再次过滤。我们可以使用这样一串过滤器中的任一个方法来完成某种特殊的操作。关于这一点在第二个例子中有更明白的阐述。
7.5.1 例1:各类数据的I/O
第一个例子(例7.3)演示了对各类数据的输入输出。
例7.3FilterIODemo1.java。
1: import java.io.*;
2: public class FilterIODemo1{
3: public static void main(String args[]) throws IOException{
//串接过滤器
4: BufferedOutputStream bufOut=
5: new BufferedOutputStream(new FileOutputStream("text.txt" );
6: DataOutputStream dataOut = new DataOutputStream(bufOut);
//用DataOutputStream类写各种数据
7: dataOut.writeBoolean(true);
8: dataOut.writeChar('a');
9: dataOut.writeInt(1);
10: dataOut.writeDouble(3.3);
11: bufOut.close();
12: dataOut.close();
13: BufferedInputStream bufIn=
14: new BufferedInputStream(new FileInputStream("text.txt" );
15: DataInputStream dataIn= new DataInputStream(bufIn);
//用DataInputStream类读各种数据
16: System.out.println(dataIn.readBoolean());
17: System.out.println(dataIn.readChar());
18: System.out.println(dataIn.readInt());
19: System.out.println(dataIn.readDouble());
20: bufIn.close();
21: dataIn.close();
22: }
23:}
例7.3的运行结果如下:(略)
上述例子演示了DataInputStream、DataOutpurStream、BufferedInputStream和BufferedOutputStream的使用。该程序中只有一个方法main()。
在方法的开头,我们实例化了BufferedOutputStream类,得到对象bufOut。注意,我们的数据输出的最终目的地是文件“Text.txt”。为了能够利用BufferedOutputStream的缓输出(把输出内容先存入缓冲,然后大块输出)功能,我们在文件输出应对上加一个过滤器,形成:
数据→过滤器对象bufOut→文件输出流
这样,我们用dataOut来写数据,就可以直接把各种类型的数据写入文件text.txt。
程序的后半部分几乎是第一个程序的翻版。我们在输入流上也加了过滤器,就可以用过滤器的方法来操作输入流了。
由于BufferedOutputStream和BufferedInputStream没有提供新的方法,这个例子也许会使读者产生一种错觉,好像只有最外层(最接近“数据”)的过滤器才能操纵输入。事实并非如此,我们将在下一个例子中说明这一点。
我们要解释的问题是,如果我们读数据时,选用的读方法与写时不一致会怎么样呢?读者可以自行实验一下。如果我们把
dataIn.readBoolean()换作dataIn.readChar()
读出的结果 就不正确了(注意读到的并不是字符‘t’),各种类型的数据存储的格式是不同的。虽然我们得到了图7.4所示的结果,但如果你用type命令看一下text.txt,将会看到不同的输出。因此,不要把程序的输出和数据的内部在存储混 为一谈。当使用dataI/O时,应当对你要读的数据的类型心中有数。DataInputStream并不能从一堆数据中析取你所需要的那个整数。
7.5.2 过滤器类家庭
下面我们介绍例1中出现的过滤器类。首先介绍一下它们的父类FilterInputStream和FilterOutputStream。
1.FilterInputStream类
这是一个抽象类。它是所有过滤器输入类的父类,提供了从一个输入流创建另一个输入流的方法。
构造函数:
■public FilterInputStream(InputStream in)
人一个输入流构造过滤器输入流。
方法:
重写了InputStream的同名方法,未提供新的方法。
2.FilterOutputStream类
与FilterOutputStream相对应,提供从一个输出流创建另一个输出流的方法。
构造函数:
■public Filer OutputStream(OutputStream out)
由输出流创始创建一个过滤器输出流。
方法:
重写了OutputStream的同名方法。
3.BufferedInputStream类
从这个类开始,我们来介绍例7.3中用到的过滤器子类。BufferedInputStream类提供了一种“预输入”功能,它把输入数据在其缓冲区内暂存,在适当的时候把较大块的数据提交出去。
构造函数:
■public BufferedInputStream(InputStream in)
■public BufferedInputStream(InputSteam in,int size)
其中size指缓冲区大小。
方法:
重写了父类的方法。其中skip()、available()、mark()、reset()均为同步(synchonized)方法。
4. BufferedOutputStream类
提供“缓输出”功能,把输出数据暂存后,在适当时候大批送出。
构造函数:
■public BufferedOutputStream(OutputStream out)
■public BufferedOutputStream(OutputStream out,int size)
方法:
■public synchronized void write(int b) throws IOException
■public synchronized void write(byte b[],int offset,int length) throws IOException
■public synchronized void flush() throws IOException
以上方法重写了父类的同名方法。
在BufferedI/O类中,还有一些protect型的成员变量,是关于缓冲区和标记的,这里就不一一列出了。
5. DataInput接口和DataOutput接口
要介绍Data I/O类,就必须介绍Data I/O接口。
Data I/O类的目的是从流中析取或向流中写入指定的数据对象。一个流可以是纯字符流,也可以包含许多类型的数据。DataInput接口和DataOutput接口就提供了从流中析取和写入数据的方法。用于读的方法除了个别之外都是无参的,写的方法则往往以被 写的数据类型为参数。方法的名字也很好记,即为“read”或“write”后接类型名,如readInt(),readUnsignedByte(),writeInt(),writeUnsignedByte()等。这引起方法均可能抛出I/O异常。一般说来,读时遇文件尾时抛出EOFException(是IOException的子类),读写时发生其它错误抛出IOException。
除了上面所说的名字很有规律的方法外,Data I/O接口中还有几个方法:
■public abstract void readFully(byte buffer[])
读全部数据到buffer[]数组。读时系统处于阻塞状态。
■public abstract void readFully(byte buffer[],int offset,int length)
把数据读到数组buffer[]中从Offset开始长为length的地方。
■public abstract int skipBytes(int n)
跳过规定字节数。返值为实际跳过的字节数。
■public abstract String readLine()
读取一行数据。
此外,还有我们早已熟悉的write()方法的三种重载形式。
6. DataInputStream类
介绍过两个数据I/O的接口后,介绍数据I/O流类就简单多了。DataInputStream类实现了DataInput接口,因面也就实现了这个接口的所有成员方法。此外,还有两个read()方法:
■public final int read(byte b[])
■public final int read(byte b[],int offset,int length)
重写了FilterInputStream的同名方法。
DataInputStream只有一个构造函数。像所有过滤器输入流类一样,这个构造函数的参数是InputStream的一个对象。
7.DataOutputStream类
这个类的成员方法我们都很熟悉了。除了实现DataOutput接口的方法之外,再就是一个flush()方法。write()与flush()重写了FilterOutputStream类的同名方法。
现在我们可以回过头来再看一下例7.3,印证一下刚才讲过的内容。这个例子的重点之一是演示过滤器的“连接”,另一个是介绍相应的类。
7.5.3 例2:行号与“可推回”的流
在下面的例子(例7.4)中,我们将进一步理解过滤器的连接问题。前面例子基本上用的都是字节流类,这个例子使用字符流类。
例7.4 FilterIODemo2.java。
1:import java.io.*;
3 ublic class FilterIODemo2{
4: public static void main(String args[]) throws IOException{
5: String s="this is a multi-line string.\n It is \nused to demo filterIO.\n";
6: char array[]=new char[s.length()];
7: for(int i=0;i<s.length();++i)
8: array=s.charAt(i);
//创建字符流,串接过滤器
9: CharArrayReader charReader = new CharArrayReader(array);
10: PushbackReader pushReader = new PushbackReader(charReader);
11: LineNumberReader lineReader = new LineNumberReader(pushReader);
12: String line;
//读字符流,加行号输出
13: while((line = lineReader.readLine())!=null){
14: System.out.println(lineReader.getLineNumber()+":"+line);
15: }
//指针置到开头
16: try{ pushReader.reset();}catch(IOException e){}
//读字符流,每读到一个'\n'就把它推回
17: boolean eof = false;
18: boolean met = false;
19: while(!eof){
20: int c=pushReader.read();
21: if(c==-1) eof=true;
22: else if(((char)c=='\n')&&!met){met =true;pushReader.unread(c);}
23: else met =false;
24: if(c!=-1) System.out.print((char)c);
25: }
26: System.out.flush();
27: pushReader.close();
28: charReader.close();
29: lineReader.close();
30: }
31:}
该程序的运行结果如下:(略)
这个例子的功能是:给一个字符串加上行号后输出;把每个换行符都“重复”一次,即每次换行时加一个空行。该例子使用的是字符流I/O,演示了几个类的使用:CharArrayReader,PushbackReader,LineNumberReader。此外,我们还可以复习一下前面提到的几个流类。
PushbackReader,顾名思义是是可以把数据“推回”输入流的流类。我们用它来实现对换行符的重复——只要读完后把“推回去”,下次就可再读一遍了。LineNumberReader可以追踪输入的行数,用它来实现加行号输出。
现在来讲解一下程序。第5行中,在main()方法的开始,定义了一个字符串s。其中,有三个换行符‘\n’。然后创建一个字节数组Array[],并在接下来的for循环(第7、8行)中为它赋值。以此为参数创建了一个内存缓冲区输入流的对象。这就是我们一串过滤器的源点。注意array并不是一个输入流,相应的CaarArrayReader也不是一个过滤器。
现在考虑选用过滤器。可根据我们想要的功能来选择。既然我们要行号,那么显然最好是一行一行读数据。BufferedReader的readLine()方法正是我们需要。(readLine()方法本来是DataInputStream类的方法,但在1.1版中过时了。详细情况在第一节中已有说明。这里用DataInputStream也是可以的但编译时会警告信息。)加行号我们可以一行一行地读,也可以自己高于个变量来累计行数。当然也可以利用一个现成的类和现在的方法——选择LineNumbdrReader类及其getLineNumber()方法。由于LineNumbdrReader本身是BuffredReader类的子类,可以直接用它来逐行读数据,不必再引入BufferedReader类。为了重复写回画换行符可选用PushbackInputStream类和它的unread()方法。
下面的任务是把它们串起来,如例子所示,可将它些过滤器一个“输出”作为下一个的“输入”。第一个while循环(第13到15行)中做的事很简单;读一行信息,取得其行号,然后一些输出。
第二个while循环(第19行到25行)的工作是重写操作符。我们用pushReader来读数据。布尔量eof来标识输入是否结束,met用来标识当瓣换行符是否被推回过。当输入没有结束时,每读到一个‘\n’ 时,就不会再“推回”了,保证换行符只被重复一次。
正如前面所提到过的,一串过滤器中的任一个都可以操作数据,无论该过滤器是最先的或最末的或是中间的任何一个。
由于我们是用print()方法来输出字符的,程序结束时可能还有一部分数据在缓冲区中,没被写到屏幕上。因此我们加了一个flush()方法强制显示到屏幕上。
将用过的流都关闭(第27到29行)是一种好的编辑风格。虽然Java的“垃圾收集”系统可以回收废弃不用的资源,仍应自觉地打扫“战场”,把能回收的资源主动回收。
7.5.4 类库支持
下面详细介绍一下例7.4中新出现的类。有一点需要解释,就是字符流I/O类与字节流I/O类的继承关系并不是一一对应的。比如,字节流I/O类中的PrintStream是FilterOutputStream的子类,而对应的字符流类PrintWriter却是Writer类的子类。因此,严格地说PrintWriter并非过滤器类。但是,为了能够分六别类地研究这些类,我们不苛求这个差别,而是按照字节流I/O类的继承关系,对应地把相应字符流I/O类也看作过滤器类。
1.PushbackReader类
构造函数两个:
■public PushbackReader(Reader in,int size)
创建缓冲区大小为size的一个PushbackReader对象。
■public PushbackReader(Reader in)
创建缓冲区大小为一个字符的一个PushbackReader对象。
方法:
■public int read()
■public int read(char cbuf[],int offset,int length)
读数据。
■public void unread(int ch)
回退一个字符。当缓冲区满或发生其它输入输出的异常情况时,抛出I/O异常。
■public int avaliable()
返回缓冲区内字节个数。
■public boolean markSupported()
确认输入流是否支持标记功能。
read()、unread()、available()均可能抛出IOException。
2.LineNumberReader类
构造函数两个,与PushbackReader类似。
下面列出方法的原型,其中我们已经熟悉的,在此就不给出解释了。
■public int read() throws IOException
■public int read(char cbuf[],int offset,int length) throws IOException
■public void setLineNumber(int lineNumber)
设置行号。
■public int getLineNumber()
读行号。
■public long skip(long n) throws IOException
■public int available()throws IOException
■public void mark(int readAheadLimit)throws IOException
在当前位置作标记。从此读取readAheadLimit个字符后标记变为无效。
■public void reset()throws IOException
返回到上一标记。
3.PrintStream类和PrintWriter类
PrintStream类是过滤器类中一个不可忽视的成员,最基本的标准输出就要借助于它——我们常用的System.out变量就是PrintStream实例。与之对应的字符流类是PrintWriter类。
PrintStream有两个构造函数(在新版API中已标记为过时):
■public PrintStream(OutputStream out)
■public PrintStream(OutputStream out,boolean autoFlush)
其中,autoFlush置为true时,每当输出遇到换行符,缓冲区的内容就被强制全部输出,如同调用了一次flush()。但要注意,如果没遇到换行符,还是会有数据“憋”在缓冲区里。
方法(已熟悉的就不解释):
■public void write(int b)
■public void write(byte b,int offset,int length)
■public void flush()
■public void close()
■public void print(Object obj)
这个方法功能是非常强大的,它可以输出任何对象,而不必另加说明。此外print()方法有许多重载形式,即有多种参数。它们是字符串(String)、字符数组(char[])、字符(char)、整数(int)、长整数(long)、浮点数(float)、双精度浮点数(double)、布尔值(boolean)。其中,输出多个数单位的print()方法(也就是指参数为String和char[]的)是同步(synchronized)方法。
■public void println()输出一个换行符。
■public synchronized void println(Object obj)
println()方法有9个重载形式,几乎就是print()方法的翻版。唯一的区别在于println()方法都是同步的。
■public boolean checkError()
检查输出过程中有什么错误,如有,返回true值。只要输出流中出现一次错误,则出错后的任意对checkError()的调用均会返回真值。
下面介绍PrintWriter类。
如同第二节中所说,PrintWriter是JDK1.1版增加了与字节流I/O相对应的字符流I/O。但是,为了保持兼容性,原先的类几乎没有改动。再加之调试的需要,PrintStream类被保留,并且System类中的成员变量out、err仍作为它的对象。然而,PrintWriter用于大多数输出比PrintStream更为合适。因此1.1版的API中建议新开发的代码使用PrintWriter类,并将 PrintStream类的两个构造函数标记为过时。这样,虽然使用System.out输出不会产生问题,在程序中创建新的PrintStream对象时却会产生编译时的警告。
PrintWriter类与PrintStream类的方法是对应的。有一个不同之外需提请读者注意,就是当前者的自动清空缓冲区的功能被使能时(构造函数中autoFlush置为true),仅当println()方法被调用时才自动清缓冲区,而不是像PrintStream一样遇到一个换行符就清缓冲。
到此为止,我们已介绍了各种类型的过滤器I/O类。适用于字节流和字符的各种对应过滤器类,其方法也是对应的。因此,对没有介绍的类读者可以从其对应类推理其功能。 |
|