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()反序列化创建对象

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351