Java IO7:管道流、对象流

一、前言

  前面的文章主要讲了文件字节输入流FileInputStream、文件字节输出流FileOutputStream以及对应的字节缓冲流,文件字符输入流FileReader、文件字符输出流FileWriter以及对应的字符缓冲流,这些都是常见的流类。当然,除了这些流类,Java还提供了别的流类供用户使用,接下来看一下管道流和对象流。

二、管道流

  管道流主要用于连接两个线程的通信,充当一个信息传递的管道。管道流也分为字节流(PipedInputStream、PipedOutputStream)和字符流(PipedReader、PipedWriter)。比如一个管道输出流PipedOutputStream必须和一个管道输入流PipedInputStream进行连接而产生一个通信管道,管道输出流PipedOutputStream向管道中写入数据,管道输入流PipedInputStream从管道中读取数据。管道流的工作如下图所示:

    

  举例说明管道流的用法。既然管道流的作用适用于线程之间的通信,那么势必会有发送信息的线程和接收信息的线程,先定义一个发送数据的线程,通过管道输出流的write()方法将数据写入到管道中。

public class SendDataThread implements Runnable{
    private PipedOutputStream pipedOutputStream = new PipedOutputStream();

    public PipedOutputStream getPipedOutputStream() {
        return pipedOutputStream;
    }

    @Override
    public void run() {
        String sendData = "receiveDataThread:hello!!!";
        try {
            //像管道输出流中写入数据(发送数据)
            pipedOutputStream.write(sendData.getBytes());
            pipedOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  在定义一个接收数据的线程,通过管道输入流的read()方法将数据从管道中读取过来

public class ReceiveDataThread implements Runnable{

    private PipedInputStream pipedInputStream = new PipedInputStream();

    public PipedInputStream getPipedInputStream() {
        return pipedInputStream;
    }

    @Override
    public void run() {
        byte[] bytes = new byte[1024];
        try {
            //将管道输出流中的内容读取到字节数组中
            int read = pipedInputStream.read(bytes);
            if(read != -1){
                System.out.println("发送过来的信息为:" + new String(bytes));
            }
            pipedInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  发送数据的线程和接收数据的线程都定义好了,接下来在利用管道输出流的connect()方法将管道输出流和管道输入流连接起来,main线程中测试

public class ReceiveDataThread implements Runnable{

    private PipedInputStream pipedInputStream = new PipedInputStream();

    public PipedInputStream getPipedInputStream() {
        return pipedInputStream;
    }

    @Override
    public void run() {
        byte[] bytes = new byte[1024];
        try {
            //将管道输出流中的内容读取到字节数组中
            int read = pipedInputStream.read(bytes);
            if(read != -1){
                System.out.println("发送过来的信息为:" + new String(bytes));
            }
            pipedInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  结果如下:

发送过来的信息为:receiveDataThread:hello!!!

  注意一下,PipedInputStream运用的是一个1024字节固定大小的字节数组当做循环缓冲区,写入PipedOutputStream的数据实际上保存到了对应的PipedInputStream的内部缓冲区。PipedInputStream执行读操作时,读取的数据实际上来自这个内部缓冲区。如果对应的PipedInputStream输入缓冲区已满,任何企图写入PipedOutputStream的线程都将被阻塞。而且这个写操作线程将一直阻塞,直至出现读取PipedInputStream的操作从缓冲区删除数据。

  这意味着,向PipedOutputStream写入数据的线程不应该是负责从对应PipedInputStream读取数据的那个线程(所以这里开了两个线程分别用于读写)。假定t线程试图一次对PipedOutputStream的write()方法的调用中向对应的PipedOutputStream写入2000字节的数据,在t线程阻塞之前,它最多能够写入1024字节的数据(PipedInputStream内部缓冲区的大小)。然而,一旦t被阻塞,读取PipedInputStream的操作就再也不能出现了,因为t是唯一读取PipedInputStream的线程,这样,t线程已经完全被阻塞。

 三、对象流

  Java中提供了ObjectInputStream和ObjectOutputStream这两个类来进行对象的序列化操作,是完成对象的存储和读取的输入/输出流类(字节流),只要把对象中的所有成员变量都存储起来,就等于保存了这个对象,之后从保存的对象之中再将对象读取进来就可以继续使用此对象。ObjectInputStream、ObjectOutputStream可以帮助开发者完成保存和读取对象成员变量取值的过程,但要求读写或存储的对象必须实现了Serializable接口。

  举例:定义一个Person类,完成该对象的存储和读取

public class Person implements Serializable{

    private static final long serialVersionUID = 8411755225402460289L;

    private String name;
    private transient int age;
    private final String country = "中国";

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", country='" + country + '\'' +
                '}';
    }
}

  进行对象的存储和读取(序列化和反序列化)

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        File file = new File("D:" +File.separator + "person.txt");
        OutputStream outputStream = new FileOutputStream(file);
        //创建对象输出流
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        Person person = new Person("zhangsan",15);
        //将对象存储到文件中
        objectOutputStream.writeObject(person);
        objectOutputStream.close();

        //创建对象输入流
        InputStream inputStream = new FileInputStream(file);
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        //读取文件中的对象信息
        Person person1 = (Person) objectInputStream.readObject();
        System.out.println(person1);
        objectInputStream.close();
    }
}

  结果:person.txt文件的内容

    

  看到乱码,因为序列化之后本身就是按照一定的二进制格式组织的文件,这些二进制格式不能被文本文件所识别,所以乱码也是正常的。  

  控制台输出结果:  

Person{name='zhangsan', age=0, country='中国'}

  可以看到age为0,而不是15,这也证明了对象中被transient修饰的成员变量不会被序列化。

参考资料:Java IO7:管道流、对象流

posted @ 2019-03-09 17:02  吹灭读书灯  阅读(365)  评论(0编辑  收藏  举报