牛骨文教育服务平台(让学习变的简单)

Java编程那些事儿90——装饰流使用1

陈跃峰

出自:http://blog.csdn.net/mailbomb

11.3.3 装饰流使用

除了按照流的方向可以把流划分为输入流和输出流两类,按照流读写数据的基本单位把流划分为字节流和字符流两类以外,还可以按照流是否直接连接实际数据源,例如文件、网络、字节数组等,将流又可以划分为实体流和装饰流两大类。

其中实体流指直接连接数据源的流类,如前面介绍的FileInputStream/FileOutputStream和FileReader和FileWriter,该类流直接实现将数据源转换为流对象,在实体流类中实现了流和数据源之间的转换,实体流类均可单独进行使用。

而装饰流指不直接连接数据源,而是以其它流对象(实体流对象或装饰流对象)为基础建立的流类,该类流实现了将实体流中的数据进行转换,增强流对象的读写能力,比较常用的有DataInputStream/DataOutputStream和BufferedReader/BufferedWriter等,装饰流类不可以单独使用,必须配合实体流或装饰流进行使用。

由于装饰流都是在已有的流对象基础上进行创建的,所以这种创建流的方式被称作“流的嵌套”,通过流的嵌套,可以修饰流的功能,例如使读写的速度增加或者提供更多的读写方式,方便数据格式的处理。

装饰流不改变原来实体流对象中的数据内容,只是从实体流对象基础上创建出的装饰流对象相对于实体流对象进行了一些功能的增强。

流的嵌套是学习IO编程时必须掌握的知识,使用它才可以让你真正体会到IO类设计时的设计思路,也可以方便的使用IO类。

下面分别以DataInputStream/DataOutputStream和BufferedReader/BufferedWriter为例子,详细介绍装饰类的使用。

11.3.3.1 DataInputStream/DataOutputStream

在前面的示例中,在向流中写入的数据必须首先转换为byte数组或char数组,当写入的数据比较少、比较简单时,则向流中写入数据时还是不是很麻烦的,但是如果向流中写入数据比较多时,手动转换数据格式则会比较麻烦。当然,很多文件都是根据文件存储的需要设计了专门的存储格式,但是这些格式一般都比较复杂,需要阅读专门的格式文档才可以读写这些特定格式的文件。

为了简化程序员对于流的操作,使得程序员可以从繁杂的数据格式中解脱出来,在IO类中专门设计了两个类——DataInputStream/DataOutputStream类简化流数据的读写,使用这两个类,可以实现以增强型的读写方法读写数据,使得读写流的数据变得比较简单。

在实际使用这两个类时,必须匹配起来进行使用。也就是说,只有使用DataOutputStream流格式写入的数据,在实际读取时才可以使用DataInputStream进行读取。因为在使用DataOutputStream向流中写入数据时,除了写入实际的数据内容以外,还写入了特定的数据格式,该格式对于程序员来说是透明的,这种特定的格式不需要程序员熟悉,而只需要使用DataInputStream读取即可,读取时的顺序和写入时的顺序和类型保持一致即可。

在DataInputStream类中,增加了一系列readXXX的方法,例如readInt、readUTF、readBoolean等等,而在DataOutputStream类中,也增加了一系列writeXXX的方法,例如writeInt、writeUTF、writeBoolean等等,使得对于数据的读写更加方便很容易。

下面以读写文件为例子,演示DataInputStream/DataOutputStream类的基本使用。

/**

 * 模拟需要存储到文件中的数据

 * 该类中保存4种类型的数据

 */

public class MyData {

         boolean b;

         int n;

         String s;

         short sh[];

         

         public MyData(){}

         

         public MyData(boolean b,int n,String s,short sh[]){

                   this.b = b;

                   this.n = n;

                   this.s = s;

                   this.sh = sh;

         }

}

在该示例中,需要将MyData类型的对象内部保存的数据按照一定的格式存储到文件中,这里列举了2种基本数据类型boolean和int,以及两种引用数据类型String和数组,在下面的示例代码中将会以一定的格式写入到文件中。

import java.io.*;

/**

 * 使用DataOutputStream书写具有一定格式的文件

 */

public class WriteFileUseDataStream {

         public static void main(String[] args) {

                   short sh[] = {1,3,134,12}; 

                   MyData data =new MyData(true,100,"Java语言",sh);

                   //写入文件

                   writeFile(data);

         }

         

         /**

          * 将MyData对象按照一定格式写入文件中

          * @param data 数据对象

          */

         public static void writeFile(MyData data){

                   FileOutputStream fos = null;

                   DataOutputStream dos = null;

                   try{

                            //建立文件流

                            fos = new FileOutputStream("test.my");

                            //建立数据输出流,流的嵌套

                            dos = new DataOutputStream(fos);

                            //依次写入数据

                            dos.writeBoolean(data.b);

                            dos.writeInt(data.n);

                            dos.writeUTF(data.s);

                            //写入数组

                            int len = data.sh.length;

                            dos.writeInt(len); //数组长度

                            //依次写入每个数组元素

                            for(int i = 0;i < len;i++){

                                     dos.writeShort(data.sh[i]);

                            }

                   }catch(Exception e){

                            e.printStackTrace();

                   }finally{

                            try {

                                     dos.close();

                                     fos.close();

                            } catch (Exception e2){

                                     e2.printStackTrace();

                            }

                   }

         }

}

在该示例代码中,首先建立一个实体流fos,该实体流连接到数据源——文件,然后以该实体流对象为基础,使用流的嵌套,建立装饰流对象dos,由于需要写入流中的对象data中包含的数据比较多,所以需要以一定的格式写入流,这里使用DataOutputStream避免自定义数据格式,而写入流中的顺序就是该流的格式,也就是文件test.my的格式,这种格式对于程序员来说是透明的。

使用对象dos中对应的writeXXX方法依次将需要存储的数据写入流中,在写入字符串时,为了使字符编码保持一致,一般使用writeUTF写入字符串,也就是先将字符串转换为utf-8格式的byte数组,然后再将该数组以一定的格式写入到流中。而在写入数组时,则首先写入数组的长度,然后再将数组的内容依次写入到流中,使用这种方式就可以很方便的将数组写入到流中。

这样文件test.my文件就具有了自己特定的文件格式,程序员需要记忆的就是该文件在写入时的写入顺序,可以很方便的使用DataInputStream读取出来。

下面的代码是使用DataInputStream读取test.my文件的代码,注意文件格式的处理。

import java.io.*;

/**

 * 使用DataInputStream读取自定义格式的文件

 */

public class ReadFileUseDataStream {

         public static void main(String[] args) {

                   MyData data = readFile();

                   System.out.println(data.b);

                   System.out.println(data.n);

                   System.out.println(data.s);

                   int len = data.sh.length;

                   for(int i = 0;i < len;i++){

                            System.out.println(data.sh[i]);

                   }

         }

         

         /**

          * 从文件test.my中读取数据,并使用读取到的数据初始化data对象

          * @return 读取到的对象内容

          */

         public static MyData readFile(){

                   MyData data = new MyData();

                   FileInputStream fis = null;

                   DataInputStream dis = null;

                   try {

                            //建立文件流

                            fis = new FileInputStream("test.my");

                            //建立数据输入流,流的嵌套

                            dis = new DataInputStream(fis);

                            //依次读取数据,并赋值给data对象

                            data.b = dis.readBoolean();

                            data.n = dis.readInt();

                            data.s = dis.readUTF();

                            int len = dis.readInt();

                            data.sh = new short[len];

                            for(int i = 0;i < len;i++){

                                     data.sh[i] = dis.readShort();

                            }                           

                   } catch (Exception e) {

                            e.printStackTrace();

                   }finally{

                            try {

                                     dis.close();

                                     fis.close();

                            } catch (Exception e) {

                                     e.printStackTrace();

                            }

                   }

                   return data;

         }

}

在该示例代码中,首先建立实体流fis,然后以该流对象为基础建立dos装饰流,然后按照写入文件的顺序,依次将流中的数据读取出来,并将读取到的数值赋值给data对象中对应的属性,从而实现将数据从文件中恢复到实际的对象。

最后再次强调,DataInputStream和DataOutputStream必须匹配起来进行使用,也就是使用DataInputStream读取的流数据必须是使用DataOutputStream流写入的数据,这样才能保持格式上的统一。

当然,使用DataInputStream和DataOutputStream和其它的实体流也可以匹配起来进行使用,例如和ByteArrayInputStream和ByteArrayOutputStream匹配使用将可以实现方便的把数据转换为特定格式的byte数组以及将byte数组恢复回来,使用的格式和上面的示例类似,这里就不再重复了。