初探Java序列化(2)-writeObject/readObject

        上一篇《初探Java序列化(Serialization)》给我们大体介绍了什么是序列化和反序列化,以及解析了一下序列化出来的文件。接着我们看看JDK具体如何序列化一个Object。

        在序列化过程中,虚拟机会试图调用对象类里的writeObject() 和readObject(),进行用户自定义的序列化和反序列化,如果没有则调用ObjectOutputStream.defaultWriteObject() 和ObjectInputStream.defaultReadObject()。同样,在ObjectOutputStream和ObjectInputStream中最重要的方法也是writeObject() 和 readObject(),递归地写出/读入byte

        所以用户可以通过writeObject()和 readObject()自定义序列化和反序列化逻辑。对一些敏感信息加密的逻辑也可以放在此。【不过此处不会检查serialVersionUID】

        对于一个Obj来说,都是先写类信息description,再写属性field。

        下面是defaultWriteObject()和defaultReadObject(),详见JDK1.8的ObjectOutputStream和ObjectInputStream。

    public void defaultWriteObject() throws IOException {
        SerialCallbackContext ctx = curContext;
        if (ctx == null) {
            throw new NotActiveException("not in call to writeObject");
        }
        Object curObj = ctx.getObj();
        ObjectStreamClass curDesc = ctx.getDesc();
        bout.setBlockDataMode(false);
        defaultWriteFields(curObj, curDesc);
        bout.setBlockDataMode(true);
    }
    private void defaultWriteFields(Object obj, ObjectStreamClass desc) {
        Class<?> cl = desc.forClass();
        if (cl != null && obj != null && !cl.isInstance(obj)) 
            throw new ClassCastException();
        desc.checkDefaultSerialize();
        int primDataSize = desc.getPrimDataSize();
        if (primVals == null || primVals.length < primDataSize) 
            primVals = new byte[primDataSize];
        desc.getPrimFieldValues(obj, primVals);
        bout.write(primVals, 0, primDataSize, false);

        ObjectStreamField[] fields = desc.getFields(false);
        Object[] objVals = new Object[desc.getNumObjFields()];
        int numPrimFields = fields.length - objVals.length;
        desc.getObjFieldValues(obj, objVals);
        for (int i = 0; i < objVals.length; i++) {
            if (extendedDebugInfo) 
                debugInfoStack.push(
                    "field (class \"" + desc.getName() + "\", name: \"" +
                    fields[numPrimFields + i].getName() + "\", type: \"" +
                    fields[numPrimFields + i].getType() + "\")");
            writeObject0(objVals[i], fields[numPrimFields + i].isUnshared());
    }
        从上面的代码能看出来,JAVA在序列化write的过程中,根据field的类型分成 基本类型 对象

解析基本类型 getPrimFieldValues()

        JAVA会把对象中field的相应内存地址记录起来,拼装在FieldReflector对象中,然后通过unsafe来读取其中的基本类型的值,并将其转换成最终要写出的byte[]

        【上述操作在ObjectStreamClass中完成】

        fieldRefl = getReflector(fields, this);
	FieldReflector(ObjectStreamField[] fields) {
            this.fields = fields;
            int nfields = fields.length;
            readKeys = new long[nfields];
            writeKeys = new long[nfields];
            offsets = new int[nfields];
            typeCodes = new char[nfields];
            ArrayList<Class<?>> typeList = new ArrayList<>();
            Set<Long> usedKeys = new HashSet<>();
            for (int i = 0; i < nfields; i++) {
                ObjectStreamField f = fields[i];
                Field rf = f.getField();
                long key = (rf != null) ?
                    unsafe.objectFieldOffset(rf) : Unsafe.INVALID_FIELD_OFFSET;
                readKeys[i] = key;
                writeKeys[i] = usedKeys.add(key) ? key : Unsafe.INVALID_FIELD_OFFSET;
                offsets[i] = f.getOffset();
                typeCodes[i] = f.getTypeCode();
                if (!f.isPrimitive()) 
                    typeList.add((rf != null) ? rf.getType() : null);
            }
            types = typeList.toArray(new Class<?>[typeList.size()]);
            numPrimFields = nfields - types.length;
        }
	void getPrimFieldValues(Object obj, byte[] buf) {
            if (obj == null) throw new NullPointerException();
            for (int i = 0; i < numPrimFields; i++) {
                long key = readKeys[i];
                int off = offsets[i];
                switch (typeCodes[i]) {
                    case 'Z':
                        Bits.putBoolean(buf, off, unsafe.getBoolean(obj, key));
                        break;
                    case 'B':
                        buf[off] = unsafe.getByte(obj, key);
                        break;
                    case 'C':
                        Bits.putChar(buf, off, unsafe.getChar(obj, key));
                        break;
                    case 'S':
                        Bits.putShort(buf, off, unsafe.getShort(obj, key));
                        break;
                    case 'I':
                        Bits.putInt(buf, off, unsafe.getInt(obj, key));
                        break;
                    case 'F':
                        Bits.putFloat(buf, off, unsafe.getFloat(obj, key));
                        break;
                    case 'J':
                        Bits.putLong(buf, off, unsafe.getLong(obj, key));
                        break;
                    case 'D':
                        Bits.putDouble(buf, off, unsafe.getDouble(obj, key));
                        break;
                    default: throw new InternalError();
                }
            }
        }

        【基本类型和byte的相互转换,都是在Bits类中处理的,如果你对其感兴趣,可以好好研究一下JAVA中的基本类型和byte的转换

        另外对于float和double都是先转换成long,再转成byte。

public static native int floatToRawIntBits(floatvalue);

public static native long doubleToRawLongBits(doublevalue);


解析对象类型 getObjFieldValues()

        写对象比基本类型要复杂,JDK先要查看Class是不是已经有序列化的记录,可以直接复用ClassDesc;另外需要判断对象的类型,Null,Handle,Class,Array,String,Enum等。最后在writeOrdinaryObject()中又会调用writeSerialData()和defaultWriteFields()来递归写入基本类型。

        详细可以参考ObjectOutputStream.writeObject0()如下。
    private void writeObject0(Object obj, boolean unshared) {
        boolean oldMode = bout.setBlockDataMode(false);
        depth++;
        try {
            // check for replacement object
            Object orig = obj;
            Class<?> cl = obj.getClass();
            ObjectStreamClass desc;
            for (;;) {
                Class<?> repCl;
                desc = ObjectStreamClass.lookup(cl, true);
                if (!desc.hasWriteReplaceMethod() ||
                    (obj = desc.invokeWriteReplace(obj)) == null ||
                    (repCl = obj.getClass()) == cl) break;
                cl = repCl;
            }
            if (enableReplace) {
                Object rep = replaceObject(obj);
                if (rep != obj && rep != null) {
                    cl = rep.getClass();
                    desc = ObjectStreamClass.lookup(cl, true);
                }
                obj = rep;
            }
            // if object replaced, run through original checks a second time
            if (obj != orig) {
                subs.assign(orig, obj);
                if (obj == null) {
                    writeNull();
                    return;
                } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                    writeHandle(h);
                    return;
                } else if (obj instanceof Class) {
                    writeClass((Class) obj, unshared);
                    return;
                } else if (obj instanceof ObjectStreamClass) {
                    writeClassDesc((ObjectStreamClass) obj, unshared);
                    return;
                }
            }
            // remaining cases
            if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum<?>) obj, desc, unshared);
            } else if (obj instanceof Serializable) {
                writeOrdinaryObject(obj, desc, unshared);
            } else 
                throw new NotSerializableException(cl.getName());
        } finally {
            depth--;
            bout.setBlockDataMode(oldMode);
        }
    }


        另外,我看了一下 典型的String序列化策略。第一个字符作为String的类型标识,有普通String和长String,然后接着标记长度,最后才是用UTF写内容。

    private void writeString(String str, boolean unshared) throws IOException {
        handles.assign(unshared ? null : str);
        long utflen = bout.getUTFLength(str);
        if (utflen <= 0xFFFF) {
            bout.writeByte(TC_STRING);
            bout.writeUTF(str, utflen);
        } else {
            bout.writeByte(TC_LONGSTRING);
            bout.writeLongUTF(str, utflen);
        }
    }
    void writeUTF(String s, long utflen) throws IOException {
        if (utflen > 0xFFFFL) 
            throw new UTFDataFormatException();
        writeShort((int) utflen);
        writeBytes(s);
    }


  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java高级程序设计 第5章 Java序列化机制 5.1 应用场景 5.2 相关知识5.3 实施过程 5.4 拓展知识5.5 拓展训练 5.6 课后小结5.7 课后习题 5.8 上机实训 Java高级程序设计实战教程第五章-Java序列化机制全文共15页,当前为第1页。 5.1 应用场景 在分布式环境下,当进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传输。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。 序列化是一种将对象以一连串的字节描述的过程,用于解决在对对象流进行读写操作时所引发的问题。序列化可以将对象的状态写在流里进行网络传输,或者保存到文件、数据库等系统中,并在需要时把该流读取出来重新构造一个相同的对象。 Java高级程序设计实战教程第五章-Java序列化机制全文共15页,当前为第2页。 5.2 相关知识 5.2.1 序列化的概念 5.2.2 序列化应用 5.2.3 序列化的几种方式 5.2.4 对象实现机制 Java高级程序设计实战教程第五章-Java序列化机制全文共15页,当前为第3页。 5.2.1 序列化的概念 将在内存中的各种对象的状态(也就是实例变量,不是方法)保存在磁盘中或者在网络中进行传输,并且可以把保存的对象状态再读出来。 将一个Java对象写入IO流;与此对应的,则是从IO流中恢复一个Java对象。 Java提供这种保存对象状态的机制,就是序列化。 对象序列化Java编程中的必备武器 Java高级程序设计实战教程第五章-Java序列化机制全文共15页,当前为第4页。 5.2.2 序列化应用 当你想把内存中的对象状态保存到一个文件中或者数据库中时候; 当你想用套接字在网络上传送对象的时候; 当你想通过RMI传输对象的时候。 Java高级程序设计实战教程第五章-Java序列化机制全文共15页,当前为第5页。 5.2.3 序列化的几种方式 在Java中socket传输数据时,数据类型往往比较难选择。可能要考虑带宽、跨语言、版本的兼容等问题。比较常见的做法有两种: 一是把对象包装成JSON字符串传输, 二是采用Java对象的序列化和反序列化。 随着Google工具protoBuf的开源,protobuf也是个不错的选择。 *提示:对JSON,Object Serialize,ProtoBuf 做个对比。 Java高级程序设计实战教程第五章-Java序列化机制全文共15页,当前为第6页。 5.2.4 对象实现机制 为了方便开发人员将Java对象进行序列化及反序列化Java提供了一套方便的API来支持。其中包括以下接口和类: java.io.Serializable java.io.Externalizable ObjectOutput ObjectInput ObjectOutputStream ObjectInputStream Java高级程序设计实战教程第五章-Java序列化机制全文共15页,当前为第7页。 5.2 相关知识 5.2.1 序列化的概念 5.2.2 序列化应用 5.2.3 序列化的几种方式 5.2.4 对象实现机制 Java高级程序设计实战教程第五章-Java序列化机制全文共15页,当前为第8页。 5.3 实施过程 5.3.1 任务一 使用serializable序列化实体对象 5.3.2 任务二 反序列化将Person对象从磁盘上读出 Java高级程序设计实战教程第五章-Java序列化机制全文共15页,当前为第9页。 5.3.1 任务一 使用serializable序列化实体对象 实现Serializable接口非常简单,只要让Jjava实现Serializable接口即可,无需实现任何方法。 一个类一旦实现了Serializable接口,那么该类的对象就是可序列化的。实现类的对象的序列化可以使用ObjectOutputStream,实现步骤如下: 创建ObjectOutputStream对象; 调用ObjectOutputStreamwriteObject方法输出对象。 Java高级程序设计实战教程第五章-Java序列化机制全文共15页,当前为第10页。 5.3.2 任务二 反序列化将Person对象从磁盘上读出 任务需求: 反序列化将Employee对象从磁盘上读出并修改员工信息,然后再写入到文件中。 分析: 相应的反序列化需要使用的类是ObjectInputStream,反序列化步骤如下: 创建ObjectInputStream对象; 使用ObjectInputStreamreadObject方法取出对象。 Java高级程序设计实战教程第五章-Java序列化机制全文

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值