黑马程序员第二阶段Java高级IO第21天

-------------------- android培训java培训期待与您交流!--------------------

 

过滤流与包装类

    在前面接触到了许多节点流类,如FileOutputStreamFileInputStream,这两个类只能往文件中写入字节或从文件中读取字节。在实际应用中,我们要往文件中写入或读取各种类型的数据,我们就必须先将其他类型的数据转换成字节数组后写入文件或是将从文件中读取到的字节数组转换成其他类型,这给我们的程序带来了一些困难和麻烦。如果有人给我们提供了一个中间类,这个中间类提供了读写各种类型的数据的各种方法,当我们需要写入其他类型的数据时,只要调用中间类的对应的方法即可,在这个中间类的方法内部,它将其他数据类型转换成字节数组,然后调用底层的节点流类将这个字节数组写入目标设备。我们将这个中间类叫做过滤流类或处理流类,也叫包装类,如IO包中有一个叫DataOutputStream的包装类。

    假如有个DataOutputStream类提供了往各种输出流对象中写入各种类型的数据(当然包括浮点小数)的方法。你现在所要做的工作就是:传递一个FileOutputStream输出流对象给DataOutputStream实例对象和调用DataOutputStream实例对象的用于写入浮点小数的方法。

DataOutputStream并没有对应到任何具体的流设备,一定要给它传递一个对应具体流设备的输出流对象,完成类似DataOutputStream功能的类就是一个包装类,也叫过滤流类或处理流类。

DataOutputStream包装类的构造函数语法:

       public DataOutputStream(OutputStream out)

DataOutputStream的部分方法列表:

    public final void writeBoolean(Boolean v)

    public final void writeShort(int v)

    public final void writeChar(int v)

    public final void writeInt(int v)

    public final void writeLong(long v)

    public final void writeFloat (float v)

    public final void writeDouble(double v)

    public final void writeBytes(String s)

 

应用程序、包装流类、节点流类以及目标的IO设备之间的调用关系与过程 

图片

 

BufferedInputStreamBufferedOutputStream

缓冲流为I/O流增加了内存缓冲区,对I/O进行缓冲是常见的性能优化,增加缓冲区有两个基本目的:

         允许Java I/O一次不只操作一个字节,这样提高了程序的性能。

         由于有了缓冲区,使得在流上执行skipmarkreset方法都成为可能。

BufferedInputStreamBufferedOutputStreamJava提供的两个缓冲区包装类,不管底层系统是否使用了缓冲区,这两个类在自己的实例对象中创建缓冲区。想想这种缓冲区与底层系统提供的缓冲区的区别。

对输入流进行缓冲可以实现部分字符的回流。除了InputStream中常用的readskip方法,BufferedInputStream还支持markreset方法。mark方法在流的当前位置作一个标记,该方法接收的一个整数参数用来指定从标记处开始,还能通过read方法读取的字节数,reset方法可以让以后的read方法重新回到mark方法所作的标记处开始读取数据。

注:mark只能限制在建立的缓冲区内。

BufferedOutputStream输出和OutputStream输出完全一样,只不过BufferedOutputStream有一个flush方法用来将缓冲区的数据强制输出完。与缓冲区输入流不同,缓冲区输出流没有增加额外的功能。在Java中使用输出缓冲流也是为了提高性能。

BufferedInputStream的两个构造函数:

       BufferedInputStreamInputStream in//创建32字节的缓冲输入流

       BufferedInputStreamInputStream inint size//size指定缓冲区的大小,通常缓冲区大小是内存、磁盘扇区或其他系统容量的整数倍,这样就可以充分提高I/O的性能。一个最优的缓冲区的大小,取决于它所在的操作系统、可用的内存空间以及机器的配置

BufferedOutputStream的两个构造函数:

       BufferedOutputStreamOutputStream out//创建32字节的缓冲输入流

       BufferedOutputStreamOutputStream outint size

BufferedReaderBufferedWriter

BufferedReaderreadLine方法可以一次读取一行文本,BufferedWriternewLine方法可以向字符流中写入不同操作系统下的换行符。

DataInputStreamDataOutputStream

DataOutputStream类提供了三个写入字符串的方法:

         public final void writeBytes(String s)

         public final void writeChars(String s)

         public final void writeBytes(String str)

3种方法的区别:

Java中的字符是Unicode编码,是双字节的,writeBytes只将字符串中的第一个字符的低字节的内容写入目标设备中,而writeChars将字符串中的每一个字符的两个字节的内容都写入到目标设备中。writeUTF对字符串按照UTF格式写入目标设备,UTF是带有长度头的,最开始的两个字节是对字符串进行UTF编码后的字节长度,然后才是每个字符的UTF编码。 

     图片 

关闭流栈中的最上层的流对象,将会自动关闭流栈中的所有底层流对象。

import java.io.*;

public class DataStreamTest {

      

       public static void main(String[] args) throws Exception {

              FileOutputStream fos = new FileOutputStream("count.txt");

              BufferedOutputStream bos = new BufferedOutputStream(fos);

              DataOutputStream dos = new DataOutputStream(bos);

              dos.writeUTF("ab中国");

              dos.writeChars("ab中国");//按照Unicode字符集写入每个符,一个字符用两个字节表示

              dos.writeBytes("ab中国");

              dos.close();

             

              FileInputStream fis = new FileInputStream("hello.txt");

              BufferedInputStream bis = new BufferedInputStream(fis);

              DataInputStream dis = new DataInputStream(bis);

              System.out.println(dis.readUTF());

              dis.close();

              byte [] buf = new byte[1024];

              int len = dis.read(buf);

              System.out.println(new String(buf,0,len));   //这里加上“Unicode”字符集即可正确读取writeChars方法写入的字符      

       }    

}

图片

图片

PrintStream

PrintStream类提供了一系列的printprintln方法,可以将基本数据类型的数据格式化成字符串输出。

格式化输出是指将一个数据用其字符串格式输出,如我们使用print方法把97这个整数打印到一个文件中,该方法将把'9''7'这两个字符的ASCII码写入到文件中,也就是文件中会被写入两个字节,这两个字节中的数字分别为5716进制的0x39)和5516进制的0x37),在记事本程序中显示为'9''7'这两个字符,如果我们使用write方法把97这个整数写到一个文件中,则写入文件中的数据就只有一个字节,字节中的数字就是97,正好是字符'a'ASCII码,所以在记事本中显示为一个字符'a'

对于基本数据类型,printprintln方法会先将它们转换成字符串的形式后再输出,而不是输出原始的字节内容,如:整数123的打印结果是字符'1','2','3'所组合成的一个字符串,而不是整数123在内存中的原始字节数据。对于一个Object类型的数据,printprintln方法会先调用对象的toString方法,然后再输出toString方法返回的字符串。

格式化输出:97被格式化输出的实际字节数据为0x390x37

PrintStream3个构造函数:

       PrintStream(OutputStream out)

       PrintStream(OutputStream out, Boolean autoflush)

       PrintStream(OutputStream out,Boolean autoflush ,String encoding)

其中的autoflush控制在Java中遇到换行符(\n)时是否自动清空缓冲区,encoding是指定编码方式,关于编码方式。

PrintStream对应的PrintWriter类,即使遇到了文本换行标识符(\n),PrintWriter类也不会自动清空缓冲区。只有在设置了autoflush模式下使用了println方法后才自动清空缓冲区。

PrintWriterprintln方法能根据操作系统的不同而生成相应的文本换行标识符。在Windows下的文本换行标识符是“\r\n”,而Linux下的文本换行标识符是“\n”。如果我们希望程序能够生成平台相关的文本换行,而不是在各种平台下都用“\n”作为文本换行,我们就应该使用PrintWriterprintln方法。

ObjectInputStreamObjectOutputStream

ObjectInputStreamObjectOutputStream这两个包装类,用于从底层输入流中读取对象类型的数据和将对象类型的数据写入到底层输出流。

不能想象,我们只要把对象中的所有成员变量都存储起来,就等于保存了这个对象,我们只要读取到一个对象中原来保存的所有成员变量的取值,就等于读取到了一个对象。ObjectInputStreamObjectOutputStream类,可以帮我们完成保存和读取对象成员变量取值的过程,但要读写或存储的对象必须实现了Serializable接口,Serializable接口中没有定义任何方法,仅仅被用作一种标记,以被编译器作特殊处理。ObjectInputStreamObjectOutputStream类不会保存对象中的transientstatic类型的成员变量,使用ObjectInputStreamObjectOutputStream类保存和读取对象的机制叫序列化,如下面定义了一个可以被序列化的MyClass类:public class MyClass implements Serializable

{

       public transient Thread t;//因为线程跟对象没有什么关系,所以这里声明为transien

       private String customerID;

       private int total;

}

 

MyClass类的实例对象被序列化时,成员变量t不会被保存和读取。

序列化的好处在于:它可以将任何实现了Serializable接口的对象转换为连续的字节数据,这些数据以后仍可被还原为原来的对象状态,即使这些数据通过网络传输也没问题。序列化能处理不同操作系统上的差异,我们可以在Windows上产生某个对象,将它序列化存储,然后通过网络传到Linux机器上,该对象仍然可以被正确重建出来,在这期间,我们完全不用担心不同机器上的不同的数据表示方式。

下面我们创建一个学生对象,并把它输出到一个文件(mytext.txt)中,然后再把该对象读出来,将其还原后打印出来:

 

import java.io.Serializable;

public class SerializableTest implements Serializable { 

 

       int ID;  String name;  int age;  String department;

      

       public SerializableTest(int ID,String name,int age,String department) {

              this.ID = ID;  this.name = name;  this.age = age;  this.department = department;

       }

      

       public String toString() {

              return "ID:" + ID + ",姓名: " + name + ",年龄: " + age + "," + department + "";

       }

}

 

import java.io.*;

publicclass Serializable {

    public static void main(String args[])throws Exception {

      

       SerializableTest stu1 = new SerializableTest(1,"戴振良",25,"Java软件开发");

       SerializableTest stu2 = new SerializableTest(2,"戴竹飞",21,".NET软件开发");

      

       FileOutputStream fos = new FileOutputStream("student.txt");

       ObjectOutputStream oos = new ObjectOutputStream(fos);

       oos.writeObject(stu1);

       oos.writeObject(stu2);

       oos.close();

      

       FileInputStream fis = new FileInputStream("student.txt");

       ObjectInputStream ois = new ObjectInputStream(fis);

       SerializableTest stu3 = (SerializableTest)ois.readObject();

       SerializableTest stu4 = (SerializableTest)ois.readObject();

       ois.close();

      

       System.out.println(stu3);

       System.out.println(stu4);      

      

    }  

}

图片

字节流与字符流的转换

InputStreamOutputStream及其子类是字节流

ReaderWriter及其子类是字符流

有时候我们需要在字节流和字符流之间进行转换

InputStreamReaderOutputStreamWriter,是用于将字节流转换成字符流来读写的两个类,InputStreamReader可以将一个字节流中的字节解码成字符后读取,OutputStreamWriter将字符编码成字节后写入到一个字节流中。

InputStreamReader的两个主要的构造函数:

       InputStreamReaderInputStream in

       InputStreamReaderInputStream inString CharsetName

OutputStreamWriter人两个主要的构造函数:

       OutputStreamWriterOutputStream out

       OutputStreamWriterOutputStream outString CharsetName

为了达到最好的效率,避免频繁地在字符与字节间的相互转换,最好不要直接使用InputStreamReaderOutputStreamWriter类来读写数据,应以是使用BufferedWriter类包装OutputStreamWriter类,用BufferedReader类包装InputStreamReader。例如:

BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));

BufferedReader in = new BufferedReader(new InputStreamReader(System in));

 

实例:读取从键盘输入的一行字符:

import java.io.BufferedReader;

import java.io.InputStreamReader;

public class ReadLine { 

       public static void main(String[] args) throws Exception {

              InputStreamReader in = new InputStreamReader(System.in);

              BufferedReader bufIn =new BufferedReader(in);

              String str = bufIn.readLine();

              System.out.println(str);

       }    

}

 

 

Java程序与其它进程的数据通信

Java程序中可以用Process类的实例对象来表示子进程,子进程的标准输入和输出不再连接到键盘和显示器,而是以管道流的形式连到父进程的一个输出流和输入流对象上。

调用Process类的getOutputStreamgetInputStream方法可以获得连接到子进程的输出流和输入流对象。

实例:在TestInOut类中启动java.exe命令执行另外一个MyTest类,TestInOutMyTest通过进程间的管道相互传递数据。

import java.io.*;

public class TestInOut implements Runnable {

       Process p = null;   

      

       public TestInOut() {

              try {

                     p = Runtime.getRuntime().exec("java MyTest");

                     new Thread(this).start();             

              } catch (Exception e) {

                     e.printStackTrace();

              }           

       }

      

       public void send() throws Exception { 

              OutputStream out = p.getOutputStream();   

              int count = 0;

              while(true) {

                     out.write(new String().valueOf(count).getBytes());

                     count++;              

                     out.write("help\r\n".getBytes());                 

              }                         

       }

      

       public void run() { 

              InputStream in = p.getInputStream();

              BufferedReader bufr = new BufferedReader(new InputStreamReader(in));      

              try{

                     while(true) {                                     

                            System.out.println(bufr.readLine());                  

                     }

              }catch(Exception e) {

                     e.printStackTrace();

              }                  

       }

      

       public static void main(String[] args) throws Exception {

              TestInOut tio = new TestInOut();

              tio.send();

       }    

}

 

 

import java.io.*;

 

public class MyTest {

       public static void main(String args[]) throws Exception {

              BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

              while(true) {                

                     System.out.println("OO:" + bufr.readLine());      

              }           

       }    

}

 

//验证管道缓冲区满后,将发生下面的哪种情况:

//1、新的数据写入时,将最前面写入的数据挤出去

//2、与PipedInputStream相连的PipedOutputStream无法再写入新的数据,

//   PipedOutputStream.write方法处于阻塞状态

//3、与PipedInputStream相连的PipedOutputStream无法再写入新的数据,

//   PipedOutputStream.write方法抛出异常。

提高程序的运行效率:

1

for(int i=0;i<str.length();i++){…}

与下面代码比较:

int len=strlength();

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

第一种代码会每循环一次都调用执行一次length()方法里的代码

2

While(true){

       Byte [] buf = new byte[1024];

       buf元素的操作语句。

}

与下面的代码比较:

Byte [] buf = new byte[1024];

While(true){

       buf元素的操作语句。

}

第一种代码每循环一次就新建一个数组对象

字节输入流类 

图片 

字节输出流类 

图片

字符输入流类 

图片

字符输出流类 

图片

Decorator设计模式

通过包装类,就可以用一个对象(the Decorators)包装另外的一个对象,比如:可以用BufferedReader来包装一个FileReaderFileReader仅仅提供了底层的读操作,比如read(char[] buffer)BufferedReader实现了一个更高层次上的操作,比如readLine,完成读取文件中的一行。其实,这是一种被称为Decorator的设计模式。、

如果要设计自己的IO包装类,这个类需要继承以FilterXXX命名的类,从而扩展了对输入输出流的支持。例如,设计一对输入输出包装类:RecordInputStreamRecordOutputStream,来完成从数据库存文件中读取记录和往数据库文件中写入记录。以后程序中就可以用它们包装InputStreamOutputStream,从而完成对数据库的操作。

Exception类从Throwable类继承的三个printStackTrace方法的定义如下:

       Public void printStackTrace()

       Public void printStackTrace(PrintStream s)

       Public void printStackTrace(PrintWriter s)

该如何把printStackTrace方法打出的详细异常信息存储到一个字符串中?

import java.io.*;

public class TestFilter {

 

       public static void main(String[] args) {

              try {

                     throw new Exception("异常");

              } catch (Exception e) {

                     StringWriter sw = new StringWriter();

                     PrintWriter pw = new PrintWriter(sw);

                     e.printStackTrace(pw);

                     System.out.println(sw.toString());

              }

       }    

}

在上面的程序中,用System.out.println将字符串打印在屏幕上来简单模拟通过网络将这个字符串发出出去。printStackTrace方法只能将异常详细信息写入一个PrintWriter对象中,写入PrintWriter对象的数据实际上会写入它所包装的一个Writer对象中,而写入StringWriter对象(一种Writer对象)的内容可以当作字符串取出来,所以用PrintWriter对象去包装一个StringWriter对象就可以解决我们的需求。解决问题的关键在于,我们如何能想到将这些流有机地串联起来。

思考与实践(1

1.       编写一个程序,将一个目录及其子目录下的所有txt类型的文本文件中的内容合并到若干个新的文本文件中,当第一个新产生的文件中存储的内容达到1K时,剩下的内容存储到第二个新的文件中,依次往下,新产生的文本文件名依次为1.txt2.txt、……。

 

解:

 

2.       用自己的话叙述什么是流,什么是节点流,什么是包装类。

 

解:流就是一串二进制代码从一个设备一个一个的不停地存到另一个设备的的形式。

节点流就是对应到具体设备的流,如文件流:FileInputStream

包装类,通过包装一个底层流来扩充底层流的功能。

3.       编写一个函数,把StringReader输入流中所有英文字母变成大写字母,并将结果写入到一个StringWriter输出流对象中。然后,用这个函数将一个字符串中的所有字符转换成大写。

 

解:

import java.io.StringWriter;

import java.io.StringReader;

 

public class ToUpperCase {

public static String toUpperCase(String str) throws Exception {

        StringReader sr = new StringReader(str);

        StringWriter sw = new StringWriter();

        char ch = '\u0000';

        char [] cbuf = new char[1024];

        int len = 0;

        len = sr.read(cbuf);

        sr.close();

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

               sw.write(Character.toUpperCase(cbuf[i]));

        }

        return  sw.toString();

}

public static void main(String[] args) throws Exception {

        String str = new String("aabbccdd");

        System.out.println(toUpperCase(str));

}    

}

4、用记事本程序创建一个内容只有“中国”这两个字符的文本文件,然后用不同的编码格式分别保存这个文本文件。有UntralEdit打开这些不同编码格式的文件,并用十六进制方式查看它们的内容,结合你所看到的内容来叙述各种编码格式之间的差异。

 

解:

ASIN编码,即gb2312编码,用两个字节表示一个中文字符,用一个字节表示一个英文字符

Unicode,Unicode little endian用两个字节表示一个字符(包括中文字符与英文字符),文件开头用FF FE表示低位优先

Unicode big endian,文件开头用FE FF表示高位优先(即高位在前)

UTF-8,文件开头用:EF BB BF,每个中文字符用3个字节表示,每个英文字符用1个字节表示

 

思考与实践(2

5、编写下面的程序代码,分析和观察程序的运行结果:

import java.io.*;

public class InputReader

{

       public static void main(String [] args) throws Exception

       {

              InputStreamReader isr = new InputStreamReader(System.in,"iso8859-1");

              BufferedReader br = new BufferedReader(isr);

              String strLine = br.readLine();

              for(int i = 0;i<strLine.length();i++)

                     System.out.println(Integer.toHexString((int)strLine.charAt(i)));

              isr.close();

              System.out.println(strLine);

       }

}

输入“中国”后,程序的运行结果如下:

d6

d0

b9

fa

???ú

请按照下面的两种要求来修改上面的程序代码,让程序能够正常打印出输入的中文字符。

1)修改程序中的语句:

       InputStreamReader isr =new InputStreamReader(System.in,”iso8859-1”);

       解:InputStreamReader isr =new InputStreamReader(System.in);

2)不修改上面的语句,而是修改下面的语句:

       System.out.println(strLine);

       解:System.out.println(new String(strLine.getBytes("iso8859-1")));

 

-------------------- android培训java培训期待与您交流!--------------------
                    详情请查看:http://edu.csdn.net/heima/
原文地址:https://www.cnblogs.com/runwind/p/4454745.html