java.io源码解析(三)--对象流(ObjectInputStream,ObjectOutputStream)

声明:本系列只供本人自学使用,勿喷。

对象流其实并不陌生,在生产中经常会使用到,比如以下2个场景。

场景1:两个进程需要在网络上进行对象的传输,比如在Web服务器中将对象信息保存到redis,怎么办?数据以什么形式传输与保存?

答:发送方将对象序列化为binary,接收方将binary反序列化为对象。

场景2:假如Web服务器中有1万用户并发访问,就有可能出现1万个Session对象,内存可能吃不消,怎么办?

答:可以先将一些session序列化到硬盘中,需要时再把保存在硬盘中的对象还原到内存中。

这时就需要对象流,填充到如图所示的IO体系。


一、对象输入流

public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants

public interface ObjectInput extends DataInput, AutoCloseable 

我们将涉及到的父类、接口都讲解一下。

1.DateInput(接口)

该接口提供的方法用于从binary中连续读取n个字节,返回对应的java基本数据类型值。

  • 核心方法
// 读取输入流,保存到byte[]
void readFully(byte b[]) throws IOException
void readFully(byte b[], int off, int len) throws IOException

// 读取数据类型对应的字节数,如readBoolean读取1个字节,readInt读取4个字节,readLong读取8个字节
boolean readBoolean() throws IOException; 
byte readByte() throws IOException;            
int readUnsignedByte() throws IOException;     
short readShort() throws IOException;
int readUnsignedShort() throws IOException;
char readChar() throws IOException;
int readInt() throws IOException;
long readLong() throws IOException;
float readFloat() throws IOException;
double readDouble() throws IOException;

//读取连续字节,直到遇到行结束符,将每个byte转换为char,因此不支持UTF
String readLine() throws IOException;
// 读取 UTF编码的binary
String readUTF() throws IOException;

2.ObjectInput(接口)

在DateInput的基础上扩展了对objects、arrays、Strings的支持

  • 核心方法
public Object readObject() throws ClassNotFoundException, IOException;

public int read() throws IOException;
public int read(byte b[]) throws IOException;
public int read(byte b[], int off, int len) throws IOException;

3.ObjectInputStream

作用:将binary反序列化为对象

注意:
1.对象的类需要implements Serializable
(子类继承实现Serializable的父类,则可以在自身不实现Serializable的情况下序列化)
2.transient或者static修饰的属性在序列化/反序列过程中会被忽略
3.反序列化创建的是新对象,不会覆盖原有的对象。
4.Externalizable接口继承自Serializable,可以选择序列化部分属性
5.反序列化枚举类不同于普通类,得到的仍然是同一个对象。

public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants{

    public ObjectInputStream(InputStream in) throws IOException {
        verifySubclass();
        bin = new BlockDataInputStream(in);
        handles = new HandleTable(10);
        vlist = new ValidationList();
        serialFilter = ObjectInputFilter.Config.getSerialFilter();
        enableOverride = false;
        readStreamHeader();
        bin.setBlockDataMode(true);
    }

以下类为ObjectInputStream的内部类

3.1 PeekInputStream

提供单字节的peek()功能

private static class PeekInputStream extends InputStream{

    private final InputStream in;

    PeekInputStream(InputStream in) {
        this.in = in;
    }
}
  • 核心方法
// 返回输入流的下一个字节,特点:连续调用peek()返回的都是同一个字节
int peek() throws IOException
// 返回输入流的下一个字节,特点:每调用一次read(),光标往后移动1个字节
public int read() throws IOException

public int read(byte[] b, int off, int len) throws IOException
// 循环read直到填满 byte[]
void readFully(byte[] b, int off, int len) throws IOException

3.2 BlockDataInputStream

blkmode字段可以让InputStream拥有两种模式:在默认模式下,输入的数据与DataOutputStream格式相同;在“Block Data”模式下,输入是由Block Data标记括起来的数据。当处于默认模式时,不预先缓冲数据; 当处于Block Data模式时,立即读取当前数据块的所有数据。

private class BlockDataInputStream extends InputStream implements DataInput{

        private final PeekInputStream in;
        private final DataInputStream din;

        BlockDataInputStream(InputStream in) {
            this.in = new PeekInputStream(in);
            din = new DataInputStream(this);
        }

        /** block data mode */
        private boolean blkmode = false;

        /******************只有当 blkmode为 true时,以下字段才有效 ********************
        /** current offset into buf */
        private int pos = 0;
        /** end offset of valid data in buf, or -1 if no more block data */
        private int end = -1;
        /** number of bytes in current block yet to be read from stream */
        private int unread = 0;

}

可以注意到这里还构造了DataInputStream对象,DataInputStream是DataInput的实现类,用来从binary中读取基本数据类型的值。

  • 核心方法
// 是否开启Block Data,true==on,false==off
boolean setBlockDataMode(boolean newmode) throws IOException

// 跳到当前 block data的end位置之后
void skipBlockData() throws IOException{
    while (end >= 0) {
        refill();
    }
}

// 读取下一个block data的头,当入参canBlock为false时,可能无法读取到完整的头。假如能读到头的话返回block data的长度
private int readBlockHeader(boolean canBlock) throws IOException{
    int avail = canBlock ? Integer.MAX_VALUE : in.available();
    // 根据in的下一个字节判断头,合理范围是(112,126)
    int tc = in.peek();
    switch (tc) {
        case TC_BLOCKDATA:读取2个字节的头,返回头的第2个字节
        case TC_BLOCKDATALONG:读取5个字节的头,返回头的第2-5个字节表示的int值
        case TC_RESET:
}

// 用block data重新填充内部缓冲区
private void refill() throws IOException{
    1.假如当前block data还有unread的数据,就将这些数据填充到buf,并更新位置的值
        int n =in.read(buf, 0, Math.min(unread, MAX_BLOCK_SIZE));
        if (n >= 0) {
           end = n;
           unread -= n;
        }
    2.假如当前block data没有unread的数据,就readBlockHeader()读取下一个头,并更新位置的值
        int n = readBlockHeader(true);
        if (n >= 0) {
           end = 0;
           unread = n;
        }
}

// 返回当前block data未消费的数据,(end - pos) + unread
int currentBlockRemaining() 

//返回流的下一个字节
int peek() throws IOException
  • 其他方法
1.DateInput接口的方法,读取基本数据类型值
public boolean readBoolean() throws IOException
public char readChar() throws IOException
public int readInt() throws IOException
...

2.读取连续的基本数据类型值到数组中
void readBooleans(boolean[] v, int off, int len) throws IOException
void readChars(char[] v, int off, int len) throws IOException
void readInts(int[] v, int off, int len) throws IOException
...

3.InputStream的方法
public int read(byte[] b, int off, int len) throws IOException
public long skip(long len) throws IOException
public int available() throws IOException
...

回到ObjectInputStream的核心方法

// 实际调用 readObject0
public final Object readObject() throws IOException, ClassNotFoundException

private Object readObject0(boolean unshared) throws IOException {

}

二. 对象输出流

ObjectOutputStream

作用:将对象序列化为binary

由于ObjectOutputStream源码结构与ObjectInputStream相似,不再深究。

public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants
{
    public ObjectOutputStream(OutputStream out) throws IOException {
        verifySubclass();
        bout = new BlockDataOutputStream(out);
        handles = new HandleTable(10, (float) 3.00);
        subs = new ReplaceTable(10, (float) 3.00);
        enableOverride = false;
        writeStreamHeader();
        bout.setBlockDataMode(true);
        if (extendedDebugInfo) {
            debugInfoStack = new DebugTraceInfoStack();
        } else {
            debugInfoStack = null;
        }
    }
}
  • 核心方法
// 实际调用 writeObject0
public final void writeObject(Object obj) throws IOException

private void writeObject0(Object obj, boolean unshared) {
        根据obj的类型来判断调用以下哪个write方法
        writeNull();
        writeHandle(h);
        writeClass((Class) obj, unshared);
        writeClassDesc((ObjectStreamClass) obj, unshared);

        writeString((String) obj, unshared);
        writeArray(obj, desc, unshared);
        writeEnum((Enum) obj, desc, unshared);
        writeOrdinaryObject(obj, desc, unshared);
}

4.demo

class Person implements Serializable {
    private static final long serialVersionUID = 42L;

    private String name;
    private int age;

    ...
}

// 继承实现 Serializable 的父类,则子类也可以序列化
class Student extends Person{
    private Double grade;

    ...
}

public class Test{
public static void main(String[] args) throws IOException, ClassNotFoundException {
        try (
                OutputStream outputStream = Files.newOutputStream(Paths.get("D:\\test.txt"));
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
                InputStream inputStream=Files.newInputStream(Paths.get("D:\\test.txt"));
                ObjectInputStream objectInputStream=new ObjectInputStream(inputStream);
                ) {
            //写入对象
            Person person = new Person();
            person.setAge(10);
            person.setName("张三");
            objectOutputStream.writeObject(person);
            //写入基本数据类型
            objectOutputStream.writeDouble(20.00);
            objectOutputStream.writeObject(new Integer(100));

            // 读取对象
            Person person1 = (Person) objectInputStream.readObject();
            // 读取基本数据类型
            objectInputStream.readDouble();
            Integer i= (Integer) objectInputStream.readObject();
            
            // 写入子类对象
            Student student=new Student();
            student.setGrade(95.5);
            student.setName("李四");
            student.setAge(20);
            objectOutputStream.writeObject(student);
            // 读取子类对象
            Student student1= (Student) objectInputStream.readObject();
        }
}

三、总结

  • 对象类需要实现Serializable
  • transient和static属性不参与序列化过程
  • objectOutputStream.writeObject()将对象序列化
  • objectInputStream.readObject()反序列化创建对象

注:由于该部分源码较多,以后有时间再详细补充,小白我得赶紧去下一个战场了。

你可能感兴趣的:(java.io源码解析(三)--对象流(ObjectInputStream,ObjectOutputStream))