Java学习之输入与输出技术I/O

输入与输出技术

  • 基本概念和I/O入门
    • 数据源
    • 流(stream)的概念
    • 经典的I/O流应用程序
    • Java中流的概念细分
    • Java中的I/O流类的体系
    • 四大I/O抽象类
  • 常用流
    • 文件字节流
    • 文件字符流
    • 缓冲字节流
    • 缓冲字符流
    • 字节数组流
    • 数据流
    • 对象流
    • 转换流
    • 随意访问文件流
  • Java对象的序列化和反序列化
    • 什么是序列化和反序列化
    • 序列化涉及的类和接口
    • 实现
  • 装饰器模式构建I/O流体系
    • 装饰器模式
    • I/O流体系中的装饰器模式

java.io包提供了相关API,可实现对所有外部系统的输入/输出操作。


基本概念和I/O入门


数据源

data-source——数据源是提供数据的原始媒介。常见的数据源有数据库、文件、其他程序、内存、网络连接、I/O设备等。
数据源分为源设备和目标设备。

  • 源设备:为程序提供数据,一般对应输入流
  • 目标设备,程序数据的目的地,一般对应输出流

流(stream)的概念

Java中对文件的操作是以流的方式进行的。流是Java内存中一组有序数据序列。Java将数据从源读入到内存中,形成流,然后这些流还可以写到另外的目的地。为什么要称之为流呢?因为数据序列在不同时刻操作的是源的不同部分。
对于输入流,程序时最终用户,通过流将数据源中的数据输送到程序中。
对于输出流,目标数据源是目的地,通过流将程序中的数据输出到目的的数据源。
输入/输出流的划分是相对于程序而言的,而不是相对于数据源。


经典的I/O流应用程序

当程序需要读取数据源的数据是,就会通过I/O流对象开启一个通向数据源的流,通过这个I/O流对象的相关方法可以顺序读取数据源中的数据。
经典写法如下:

import java.io.*;

public class Study{
     
    public static void main(String[] args) {
     
        FileInputStream fp = null;
        try{
     
            fp = new FileInputStream("study.txt");//文件内容为ABC
            StringBuilder sb = new StringBuilder();
            int temp = 0;//当temp==-1时,表示到了文件结尾,停止读取
            while((temp = fp.read()) != -1)
                sb.append((char)temp);
            System.out.println(sb);
        } catch (Exception e){
     
            e.printStackTrace();
        }finally {
     
            try{
     //这种写法保证了及时遇到异常情况也会关闭对象流
                fp.close();//流对象使用完必须关闭
                //否则总是占用系统资源,最终会造成系统崩溃
            }catch (IOException e){
     
                e.printStackTrace();
            }
        }
    }
}
//输出结果如下
ABC

Java中流的概念细分

按流的方向分类

  • 输入流:数据流向是数据源到程序(以InputStream、Reader结尾的流)
  • 输出流:数据流向是程序到目的地(以OutPutStream、Write结尾的流)

按处理的数据单元分类

  • 字节流:以字节为单位获取数据,命名上以Stream结尾的流一般是字节流,如FileInputStream、FileOutputStream
  • 字符流:以字符为单位获取数据,命名上以Reader/Writer结尾的流一般是字符流,如FileReader、FileWrite

按处理对象不同分类

  • 节点流:可直接从数据源或者目的地读写数据,如FileInputStream、FileReader、DataInputStream等
  • 处理流:不直接连接到数据源或者目的地,是“处理数据的流”。通过其它流的处理提高程序的性能,如BufferedInputStream、BufferedReader,处理流也叫包装流

节点流处于I/O操作的第一线,所有操作必须通过它们进行,处理流可以对节点流进行包装,提高性能或者提高程序的灵活性
比如如下示意图:
Java学习之输入与输出技术I/O_第1张图片


Java中的I/O流类的体系

Java提供了各式各样的I/O流,在程序中可根据需求选择合适的I/O流。下面列出I/O流类体系:
Java学习之输入与输出技术I/O_第2张图片
其中绿色是节点流,蓝色是处理流,橙色是字符流。很多流都是成对出现的,下面再给出常用类的简单总结:

  • InputStream&OutputStream:字节流的抽象类
  • Reader&Writer:字符流的抽象类
  • FileInputStream&FilterOutputStream:节点流,以字节为单位直接操作“文件”
  • ByteArrayInputStream&ByteArrayOutputStream:节点流,以字节为单位直接操作“字节数组对象”
  • ObjectInputStream&ObjectOutputStream:处理流,以字节为单位直接操作“对象”
  • DataInputStream&DataOutputStream:处理流,以字节为单位直接操作“基本数据类型和字符串类型”
  • FileReader&FileWriter:节点流,以字符为单位直接操作“文本文件”(只能读写文本文件)
  • BufferReader&BufferedWriter:处理流,将Reader/Writer对象进行包装,增加缓存功能,提高读写效率
  • BufferedInputSream&BufferedOutputStream:处理流,将InputStream&OutputStream对象进行包装,提高读写效率
  • InputStreamReader&OutputStreamWriter:处理流,将字节流对象转化成字符流对象
  • PrintStream:处理流,将OutputStream进行包装,它可以方便地输出字符,更灵活

四大I/O抽象类

InputStream
该抽象类是表示字节输入流的所有类的父亲。InputStream是一个抽象类,不可以实例化。数据的读取需要它的子类来实现。根据节点的不同,它派生了不同的节点流子类。
继承自InputStream的流都是用于向抽象中输入数据,且数据的单位为字节(8bit)。
常用方法:

  • int read():读取一个字节的数据,并将字节的值作为int类型返回(0~255间的一个值)。如果未读出字节则返回-1,返回-1代表了读取结束。
  • void close():关闭输入流对象,释放相关系统资源。

OutputStream
该抽象类表示字节输出流的所有父类。输出流接收输出字节并将这些字节发送到某个目的地。
常用方法:

  • void write():向目的地中写入一个字节
  • void close():关闭输出流对象,释放相关系统资源。

Reader
Reader用于读取的字符流抽象类,数据单位为字符。

  • int read():读取一个字符的数据,并将字符的值作为int类型返回(0~65535间的一个值,即Unicode值)。如果未读出字节则返回-1。
  • void close():关闭流对象,释放相关系统资源。

Writer
Writer用于写入的字符流抽象类。

  • void write():向输出流中写入一个字符。
  • void close():关闭输出流对象,释放相关系统资源。

常用流


文件字节流

FileInputStream通过字节的方式读取文件,适合读取所有类型文件。Java也提供了FileReader专门读取文本文件。
FileOutputStream通过字节的方式写数据到文件中,适合写入所有类型文件。Java也提供了FileWriter专门写入文本文件。
示例1:将字符串/字节数组的内容写入到文件中

import java.io.FileOutputStream;
import java.io.IOException;

public class Study{
     
    public static void main(String[] args) {
     
        FileOutputStream fp = null;
        String str = "将字符串写入文件中";
        try{
     
            fp = new FileOutputStream("study.txt");
            //该方法将一个字节数组写到文件中,而write(int n)是写入一个字节
            fp.write(str.getBytes());
        }catch (Exception e){
     
            e.printStackTrace();;
        }finally {
     
            try {
     
                if(fp != null)
                    fp.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }
        }
    }
}

文本中内容:
Java学习之输入与输出技术I/O_第3张图片
示例2:利用文件流实现文件的复制

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Study{
     
    public static void main(String[] args) {
     
        copyFile("study.txt", "copy.txt");
    }
    static void copyFile(String src, String dec){
     
        FileInputStream f1 = null;
        FileOutputStream f2 = null;
        //设置缓存数组以提高效率
        byte[] buffer = new byte[1024];
        int temp = 0;
        try {
     
            f1 = new FileInputStream(src);
            f2 = new FileOutputStream(dec);
            while((temp = f1.read(buffer)) != -1)
                f2.write(buffer, 0, temp);
        }catch (Exception e){
     
            e.printStackTrace();
        }finally {
     
            try{
     
                if(f1 != null)
                    f1.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try {
     
                if(f2 != null)
                    f2.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }
        }
    }
}

执行后如下:
Java学习之输入与输出技术I/O_第4张图片
Ps.

  • 为了减少对硬盘的读写次数,提高效率,通常设置缓存数组。相应的,读取时使用的方法为read(byte[] b);写入时的方法为write(byte[] b, int off, int length)
  • 程序中如果遇到多个流,每个流都要单独关闭,防止其中一个流出现异常后导致其他流无法关闭的情况

文件字符流

字节流可以处理所有的文件,但是字节流不能很好地处理Unicode字符,经常会出现乱码的情况。故处理文本文件时一般使用文件字符流,它以字符为单位进行操作。
示例3:使用FileReader和FileWriter实现文本文件复制

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Study{
     
    public static void main(String[] args) {
     
        copyFile("study.txt", "copy.txt");
    }
    static void copyFile(String src, String dec){
     
        FileReader fr = null;
        FileWriter fw = null;
        int len = 0;
        try{
     
            fr = new FileReader(src);
            fw = new FileWriter(dec);
            char[] buffer = new char[128];
            while((len = fr.read(buffer)) != -1)
                fw.write(buffer, 0, len);
        }catch (Exception e){
     
            e.printStackTrace();
        }
        try{
     
            if(fr != null)
                fr.close();
        }catch (IOException e){
     
            e.printStackTrace();
        }try {
     
            if(fw != null)
                fw.close();
        }catch (IOException e){
     
            e.printStackTrace();
        }
    }
}

执行后如下:
Java学习之输入与输出技术I/O_第5张图片


缓冲字节流

Java缓冲流本身并不具有I/O流的读取与写入功能,只是在其它流上加上缓冲功能以提高效率,就像是把其他的流包装起来一样,因此缓冲流是一种处理流。
当对文件或者其他数据源进行频繁读写时,效率比较低,这是如果使用缓冲流就可以更高效的写入信息。缓冲流现将数据缓存起来,当缓存区存满或者手动刷新时再一次性将数据读取到程序或者写入目的地。
BufferedInputStream和BufferedOutputStream这两个流是缓冲字节流,通过内存缓存数组来提高操作流的效率。
示例4:使用缓冲流实现文件的高效率复制

import java.io.*;

public class Study{
     
    public static void main(String[] args) {
     
        long UStime = System.currentTimeMillis();
        UsualCopyFile("d:/Test/test/老爹单曲飘向北方.mp4", "d:/Test/copy/复制.mp4");
        long Midtime = System.currentTimeMillis();
        BufferCopyFile("d:/Test/test/老爹单曲飘向北方.mp4", "d:/Test/copy/复制的.mp4");
        long BEtime = System.currentTimeMillis();
        System.out.println("普通字节流复制花费的时间:" + (Midtime - UStime));
        System.out.println("缓冲字节流复制花费的时间:" + (BEtime - Midtime));
    }
    static void BufferCopyFile(String src, String dec){
     
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        int temp = 0;
        try{
     
            fis = new FileInputStream(src);
            fos = new FileOutputStream(dec);
            //使用缓冲字节流包装文件字节流,增加缓冲功能,提高效率
            //缓存区的大小(缓存数组长度)默认是8192,也可以自己指定大小
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);
            while((temp = bis.read()) != -1)
                bos.write(temp);
        }catch (Exception e){
     
            e.printStackTrace();
        }finally {
     
            try{
     
                if(fos!=null)
                    fos.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try {
     
                if(fis!=null)
                    fis.close();;
            }catch (IOException e){
     
                e.printStackTrace();
            }try {
     
                if(bos!=null)
                    bos.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try{
     
                if(bis!=null)
                    bis.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }
        }
    }
    static void UsualCopyFile(String src, String dec){
     
        FileInputStream fis = null;
        FileOutputStream fos = null;
        int temp = 0;
        try{
     
            fis = new FileInputStream(src);
            fos = new FileOutputStream(dec);
            while((temp = fis.read()) != -1)
                fos.write(temp);
        }catch (Exception e){
     
            e.printStackTrace();
        }finally {
     
            try{
     
                if(fos!=null)
                    fos.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try {
     
                if(fis!=null)
                    fis.close();;
            }catch (IOException e){
     
                e.printStackTrace();
            }
        }
    }
}
//输出结果如下:
普通字节流复制花费的时间:391641
缓冲字节流复制花费的时间:389

缓冲字符流

BufferedReader和BufferedWriter增加了缓存机制,大大提高了读写文本文件的效率,同时提供了更方便的安行读取的方法readLine()。在处理文本时,可以使用缓冲字符流。
示例5:使用缓冲字符流实现文本文件的高效率复制

import java.io.*;

public class Study{
     
    public static void main(String[] args) {
     
        long UStime = System.currentTimeMillis();
        UsualCopyFile("博客之容器.txt", "ucopy.txt");
        long Midtime = System.currentTimeMillis();
        BufferCopyFile("博客之容器.txt", "bcopy.txt");
        long BEtime = System.currentTimeMillis();
        System.out.println("普通字符流复制花费的时间:" + (Midtime - UStime));
        System.out.println("缓冲字符流复制花费的时间:" + (BEtime - Midtime));
    }
    static void BufferCopyFile(String src, String dec){
     
        FileReader fr = null;
        BufferedReader br = null;
        FileWriter fw = null;
        BufferedWriter bw = null;
        String temp = "";
        try{
     
            fr = new FileReader(src);
            fw = new FileWriter(dec);
            br = new BufferedReader(fr);
            bw = new BufferedWriter(fw);
            //BufferReader提供了更方便的readLine()方法,直接按行读取文本
            //br.readLine()方法的返回值是一个字符串对象,即本文本中的一行内容
            while((temp = br.readLine()) != null) {
     
                //将读取的一行字符串写入文件
                bw.write(temp);
                //下次写入之前先换行,否则会在上一行后边继续追加,而不是另起一行
                bw.newLine();
            }
        }catch (Exception e){
     
            e.printStackTrace();
        }finally {
     
            try{
     
                if(fr!=null)
                    fr.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try {
     
                if(fw!=null)
                    fw.close();;
            }catch (IOException e){
     
                e.printStackTrace();
            }try {
     
                if(br!=null)
                    br.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try{
     
                if(bw!=null)
                    bw.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }
        }
    }
    static void UsualCopyFile(String src, String dec){
     
        FileReader fr = null;
        FileWriter fw = null;
        int temp = 0;
        try{
     
            fr = new FileReader(src);
            fw = new FileWriter(dec);
            while((temp = fr.read()) != -1)
                fw.write(temp);
        }catch (Exception e){
     
            e.printStackTrace();
        }finally {
     
            try{
     
                if(fr!=null)
                    fr.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try {
     
                if(fw!=null)
                    fw.close();;
            }catch (IOException e){
     
                e.printStackTrace();
            }
        }
    }
}
//输出如下
普通字符流复制花费的时间:20
缓冲字符流复制花费的时间:3

字节数组流

ByteArrayInputStream和ByteArrayOutputStream经常用在需要流和数组之间转化的情况,FileInputStream把文件当做数据源,而ByteArrayInputStream则是把内存中的“某个字节数组对象”当做数据源。
示例6:ByteArrayInputStream的使用

import java.io.ByteArrayInputStream;
import java.io.IOException;

public class Study{
     
    public static void main(String[] args) {
     
        byte[] b = "abcdefghijkl".getBytes();
        for(byte temp: b)
            System.out.print(temp + " ");
        System.out.println();
        test(b);
    }
    public static void test(byte[] b){
     
        ByteArrayInputStream bais = null;
        StringBuilder sb = new StringBuilder();
        int temp = 0;
        int num = 0;
        try{
     
            bais = new ByteArrayInputStream(b);
            while((temp = bais.read()) != -1){
     //一次读一个字节
                sb.append((char) temp);
                num++;
            }
            System.out.println(sb);
            System.out.println("读取的字节数:"+num);
        }finally {
     
            try {
     
                if(bais != null)
                    bais.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }
        }
    }
}
//输出如下
97 98 99 100 101 102 103 104 105 106 107 108 
abcdefghijkl
读取的字节数:12

数据流

数据流将“基本数据类型与字符串类型”作为数据源,从而允许程序以与机器无关的方式从底层输入/输出流中操作Java基本数据类型与字符串类型。
DataInputStream和DataOutputStream提供了可以存取与机器无关的所有Java基础类型数据的方法。
DataInputStream和DataOutputStream是处理流,可以对其他节点流或者处理流进行包装,增加一些更灵活、更高效的功能。
示例7:DataInputStream和DataOutputStream的使用

import java.io.*;

public class Study{
     
    public static void main(String[] args) {
     
        DataOutputStream dos = null;
        DataInputStream dis = null;
        FileOutputStream fos = null;
        FileInputStream fis = null;
        try{
     
            fos = new FileOutputStream("a.txt");
            fis = new FileInputStream("a.txt");
            //使用数据流对缓冲进行包装,新增缓冲功能
            dos = new DataOutputStream(new BufferedOutputStream(fos));
            dis = new DataInputStream(new BufferedInputStream(fis));
            dos.writeChar('c');
            dos.writeInt(295);
            dos.writeDouble(Math.random());
            dos.writeBoolean(true);
            dos.writeUTF("不会吧不会吧不会吧");
            dos.flush();//手动刷新缓冲区,将流中数据写入文件中
            //直接读取数去,读取数据顺序要和之前写入的一样,否则不能正确读取数据
            System.out.println("char:" + dis.readChar());
            System.out.println("int:" + dis.readInt());
            System.out.println("double:" + dis.readDouble());
            System.out.println("boolean:" + dis.readBoolean());
            System.out.println("String:" + dis.readUTF());
        }catch (Exception e){
     
            e.printStackTrace();
        }finally {
     
            try{
     
                if(dos != null)
                    dos.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try{
     
                if(dis != null)
                    dis.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try{
     
                if(fos != null)
                    fos.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try{
     
                if(fis != null)
                    fis.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }
        }
    }
}
//输出如下
char:c
int:295
double:0.12439793758984896
boolean:true
String:不会吧不会吧不会吧

对象流

如果要对某个对象进行读写操作,需要学习一对新的处理流:ObjectInputStream和ObjectOutputStream。它们以对象为数据源,但是必须对传输的对象进行序列化和反序列化的操作。序列化和反序列化在后面学习。
示例8:ObjectInputStream和ObjectOutputStream的使用

import java.io.*;
import java.util.Date;

public class Study{
     
    public static void main(String[] args) {
     
        write();
        read();
    }
    public static void write(){
     
        OutputStream os = null;
        BufferedOutputStream bos = null;
        ObjectOutputStream oos = null;
        try{
     
            os = new FileOutputStream(new File("xxx.txt"));
            bos = new BufferedOutputStream(os);
            oos = new ObjectOutputStream(bos);
            //对象流也可以用基本数据类型进行读写操作哦
            oos.writeInt(295);
            oos.writeDouble(3.69);
            oos.writeChar('a');
            oos.writeBoolean(false);
            oos.writeUTF("不会吧不会吧不会有人觉得java难学吧");
            //对象流能够对对象数据类型进行读写操作
            //Date是系统提供的类,已经实现了序列化接口
            //如果是自定义的类,则需要自己实现序列化接口
            oos.writeObject(new Date());
        }catch (Exception e){
     
            e.printStackTrace();
        }finally {
     
            try{
     if(oos != null)
                oos.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try{
     if(bos != null)
                bos.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try{
     if(os != null)
                os.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }
        }
    }
    public static void read(){
     
        InputStream is = null;
        BufferedInputStream bis = null;
        ObjectInputStream ois = null;
        try{
     
            is = new FileInputStream(new File("xxx.txt"));
            bis = new BufferedInputStream(is);
            ois = new ObjectInputStream(bis);
            System.out.println(ois.readInt());
            System.out.println(ois.readDouble());
            System.out.println(ois.readChar());
            System.out.println(ois.readBoolean());
            System.out.println(ois.readUTF());
            System.out.println(ois.readObject().toString());
        }catch (ClassNotFoundException e){
     
            e.printStackTrace();
        }catch (IOException e){
     
            e.printStackTrace();
        }finally {
     
            try{
     if(ois != null)
                ois.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try{
     if(bis != null)
                bis.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try{
     if(is != null)
                is.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }
        }
    }
//输出如下
295
3.69
a
false
不会吧不会吧不会有人觉得java难学吧
Sun Jul 12 18:51:55 CST 2020

转换流

InputStreamReader和OutputStreamWriter用来实现将字节流转换成字符流。
示例9:使用InputStreamReader接受用户输入,并输出到控制台

import java.io.*;

public class Study{
     
    public static void main(String[] args) {
     
    	//创建字符输入和输出流,使用转换流将字节转换成字符流
        BufferedReader br = null;
        BufferedWriter bw = null;
        try{
     
            br = new BufferedReader(new InputStreamReader(System.in));
            bw = new BufferedWriter(new OutputStreamWriter(System.out));
            String str = br.readLine();
            while(!"#".equals(str)){
     
                bw.write(str);//写到控制台
                bw.newLine();//写一行后换行
                bw.flush();//手动刷新
                str = br.readLine();//在读一行
            }
        }catch (IOException e){
     
            e.printStackTrace();
        }finally {
     
            if(br != null){
     
                try{
     
                    br.close();
                }catch (IOException e){
     
                    e.printStackTrace();
                }
            }if (bw != null){
     
                try {
     
                    bw.close();
                }catch (IOException e){
     
                    e.printStackTrace();
                }
            }
        }
    }
}
//示例输出如下
ab5654ad2@@&*sads13//.;.'
ab5654ad2@@&*sads13//.;.'
asds1z\cs/fsf65sa+5s61
asds1z\cs/fsf65sa+5s61
#

Process finished with exit code 0

随意访问文件流

RandomAccessFile可以实现两个作用:
1)实现对一个文件的读和写操作
2)可以访问文件的任意位置,不像其他流只能按照先后顺序读取
此流掌握三个核心方法:
1)RandomAccessFile(String name, String mode) name用于确定文件,mode取r(读)或rw(可读写),通过mode可以确定流对文件的访问权限。
2)seek(long a)用于定位流对象读写文件的位置,a确定读写位置距离文件开头的字节数。
3)getFilePointer()用于获得流的当前读写位置。
示例10:RandomAccessFile应用

import java.io.IOException;
import java.io.RandomAccessFile;

public class Study{
     
    public static void main(String[] args) {
     
        RandomAccessFile raf = null;
        try{
     
            int[] data = {
     1 , 2, 3, 5, 9, 32, 56, 75, 165, 135};
            raf = new RandomAccessFile("a.txt", "rw");
            for(int i = 0; i < data.length; i++)
                raf.writeInt(data[i]);
            raf.seek(4);
            System.out.println(raf.readInt());
            for(int i = 0; i < 10; i+=2){
     
                raf.seek(i * 4);
                System.out.print(raf.readInt() + " ");
            }
            System.out.println();
            raf.seek(8);
            raf.writeInt(88);
            for(int i = 0; i < 10; i++){
     
                raf.seek(i * 4);
                System.out.print(raf.readInt() + " ");
            }
        }catch (IOException e){
     
            e.printStackTrace();
        }finally {
     
            try{
     if(raf != null)
                raf.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }
        }
    }
}//输出如下
2
1 3 9 56 165 
1 2 88 5 9 32 56 75 165 135 

Java对象的序列化和反序列化

对象的本质是用于组织和存储数据,对象本身也是数据。


什么是序列化和反序列化

当两个进程进行远程通信时,可能会发送各种类型的数据,无论是何种类型的数据,都是以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列才能在网络上传送,接收方则需要把字节序列再恢复为Java对象才能正常读取。
将Java对象转换为字节序列的过程称为对象的序列化,将字节序列恢复为Java对象的过程称为对象的反序列化。
对象序列化的作用如下:

  • 持久化:把对象的字节序列永久的保存到硬盘上,通常存放在一个文件中。如休眠的实现。
  • 网络通信:在网络上传送对象的字节序列。如服务器间的数据通信。

序列化涉及的类和接口

ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
ObjectInputStream代表对象输入流,它的readObject()方法可以从一个源输入流中读取字节序列,再将其反序列化为一个对象并返回。
只有实现了Serializable接口的类的对象才能被序列化。Serializable接口是一个空接口,只起到标记作业。

实现

import java.io.*;

class Person implements Serializable{
     
    //添加序列化ID,它决定这是否能够成功反序列化
    private static final long serialVersionUID = 1L;
    String name;
    int age;
    boolean isMan;
    public Person(String name, int age, boolean isMan){
     
        this.name = name;
        this.age = age;
        this.isMan = isMan;
    }

    @Override
    public String toString() {
     
        return "Person [name:" + name + ", age:" + age + ", isMan:"+ isMan + "]";
    }
}
public class Study{
     
    public static void main(String[] args) {
     
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        FileInputStream fis = null;
        try{
     
            Person person = new Person("xxx", 18, true);
            //序列化
            fos = new FileOutputStream("a.txt");
            oos = new ObjectOutputStream(fos);
            oos.writeObject(person);
            oos.flush();
            //反序列化
            fis = new FileInputStream("a.txt");
            ois = new ObjectInputStream(fis);
            Person p = (Person)ois.readObject();
            System.out.println(p);
        }catch (Exception e){
     
            e.printStackTrace();
        }finally {
     
            try{
     
                if(oos != null)
                    oos.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try{
     
                if(fos != null)
                    fos.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try{
     
                if(ois != null)
                    ois.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }try{
     
                if(fis != null)
                    fis.close();
            }catch (IOException e){
     
                e.printStackTrace();
            }
        }
    }
}//输出如下
Person [name:xxx, age:18, isMan:true]

装饰器模式构建I/O流体系

装饰器模式

装饰器模式是GoF的23中设计模式中较为常用的一种模式,他可以对源有的类进行包装和修饰,是新的类具有更强的功能。
例如以下代码:

class Computer{
     
    private String name;
    public Computer(String name){
     
        this.name = name;
    }
    public void show(){
     
        System.out.println("我是" + name + ", 我的屏幕可以播放视频");
    }
}
class ComputerWithAudio{
     
    public Computer computer;
    public ComputerWithAudio(Computer c){
     
        this.computer = c;
    }
    public void show(){
     
        computer.show();
        System.out.println("我还可以播放他们的声音");
    }
}

public class Study{
     
    public static void main(String[] args) {
     
        Computer computer = new Computer("dell");
        computer.show();
        System.out.println("========装饰之后=========");
        ComputerWithAudio computerWithAudio = new ComputerWithAudio(computer);
        computerWithAudio.show();
    }
}//输出如下

我是dell, 我的屏幕可以播放视频
========装饰之后=========
我是dell, 我的屏幕可以播放视频
我还可以播放他们的声音

I/O流体系中的装饰器模式

I/O流体系中大量使用了装饰器模式,我们之前的代码实现中大量的使用了,这让流具有更强的功能以及更强的灵活性。如:

OutputStream os = new FileOutputStream(new File("xxx.txt"));
BufferedOutputStream bos = new BufferedOutputStream(os);
ObjectOutputStream oos = new ObjectOutputStream(bos);

ObjectOutputStream装饰了原有的BufferedOutputStream,BufferedOutputStream装饰了原有的OutputStream,使普通的OutputStream也具有了缓存功能和读取写入对象的功能,提高了效率,增强了灵活性。

你可能感兴趣的:(Java学习笔记)