初探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);
    }
时间: 2024-10-08 08:15:54

初探Java序列化(2)-writeObject/readObject的相关文章

初探Java序列化(Serialization)

Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化Deserialization是一种将这些字节重建成一个对象的过程.[字节流的来回转换] Java中,一切都是对象,在分布式环境中经常需要将Object从这一端网络或设备传递到另一端.这就需要有一种可以在两端传输数据的协议.Java序列化机制就是为了解决这个问题而产生. 将对象状态转换成字节流之后,可以用java.io包中各种字节流的类将其保存到文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机.对象序

Java 序列化深入分析

序列化机制介绍 ??序列化是指把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中.序列化后的字节流保存了Java对 象的状态以及相关的描述信息.客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象.本质上讲,序列化就是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态.序列化机制的核心作用就是对象状态的 保存与重建. Java序列化机制解析 ??Java API提供了对序列化的支持,要实

Java 序列化与反序列化

1.什么是序列化?为什么要序列化? Java 序列化就是指将对象转换为字节序列的过程,而反序列化则是只将字节序列转换成目标对象的过程. 我们都知道,在进行浏览器访问的时候,我们看到的文本.图片.音频.视频等都是通过二进制序列进行传输的,那么如果我们需要将Java对象进行传输的时候,是不是也应该先将对象进行序列化?答案是肯定的,我们需要先将Java对象进行序列化,然后通过网络,IO进行传输,当到达目的地之后,再进行反序列化获取到我们想要的对象,最后完成通信. 2.如何实现序列化 2.1.使用到JD

Java序列化(Serialization)

关于Java的序列化的文章在网上已经够多了,在这里写关于Java序列化的文章是对自己关于这方面的的一种总结,结合以前的开发经验与网上的资料,写了这篇文章,对自己是有着巩固记忆的作用,也希望能够对大家有一定帮助. 一.什么是序列化(Serialization)? 序列化是Java提供的一种机制,将对象转化成字节序列,在字节序列中保存了对象的数据.对象的类型的信息与存储在对象中的数据的类型.序列化实际上就是将保存对象的"状态",可以方便以后的程序使用或者通过网络传输到另一台主机使用.一般来

【总结】你所不知道的Java序列化

我们都知道,Java序列化可以让我们记录下运行时的对象状态(对象实例域的值),也就是我们经常说的对象持久化 .这个过程其实是非常复杂的,这里我们就好好理解一下Java的对象序列化. 1. 首先我们要搞清楚,Java对象序列化是将 对象的实例域数据( 包括private私有域) 进行持久化存储.而并非是将整个对象所属的类信息进行存储. 其实了解JVM的话,我们就能明白这一点了.实际上堆中所存储的对象包含了实例域数据值以及指向类信息的地址,而对象所属的类信息却存放在方法区中.当我们要对持久层数据反序

深入分析java序列化

概念 先来点简单的概念: what?why? 什么是序列化?为什么要序列化? 答曰:将java对象转成字节序列,用以传输和保存 where? 使用场景是什么? 答曰:对象的传输:状态的备份,例如jvm的dump文件: 好了,不装*了,下面说的详细点.其实对象的序列化主要有两种用途: 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中 在网络上传送对象的字节序列 在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存.比如最常见的是Web服务器中的Sessio

Java序列化的理解与学习

1.什么是Java序列化 Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比 JVM的生命周期更长.但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象.Java对象序列 化就能够帮助我们实现该功能. 必须注意地是,对象序列化保存的是对象的"状态",即它的成员变量.由此可知,对象序列化不会关注类中的静态变量. 所谓序列化其实就是将程序中的数据(对

关于java序列化中的一个细节

java序列化机制的可以参考很多资料了,最近在看的时候发现了一些问题. 1. 默认的序列化机制,很多书里讲到序列化类只序列化类名,实例变量,不会实例化类变量(static)和瞬态变量(transient). 我使用1.6,1.7,1.8测试了一下,static都是可以被序列化的. 测试代码: 1 public class Logg implements Serializable{ 2 3 private static String name; 4 private transient String

你所不知道的Java序列化

我们都知道,Java序列化可以让我们记录下运行时的对象状态(对象实例域的值),也就是我们经常说的对象持久化 .这个过程其实是非常复杂的,这里我们就好好理解一下Java的对象序列化. 1. 首先我们要搞清楚,Java对象序列化是将 对象的实例域数据( 包括private私有域) 进行持久化存储.而并非是将整个对象所属的类信息进行存储. 其实了解JVM的话,我们就能明白这一点了.实际上堆中所存储的对象包含了实例域数据值以及指向类信息的地址,而对象所属的类信息却存放在方法区中.当我们要对持久层数据反序