IO对象序列化

举报
学海无涯yc 发表于 2022/07/20 18:56:20 2022/07/20
【摘要】 目录1 前言2 正文2.1 入门2.2 流程分析2.2.1 序列化流程分析创建 ObjectOutputStream 对象,写入流的头信息writeObject(Object obj) 方法writeObject0(Object obj, boolean unshared) 方法writeOrdinaryObject(Object obj, ObjectStreamClass desc, b...

目录


1 前言

本文会通过简单的例子介绍如何对实现了 Serializable 接口的类进行序列化和反序列化,这部分是使用 Serializable 的入门;接着会重点分析序列化步骤与反序列化步骤,这部分会分析源码,加深对原理的理解;最后会列举实际开发中使用 Serializable 接口会遇到的问题并一一进行解决,这部分对开发中会遇到的问题进行填坑。

2 正文

2.1 入门

在实际开发中,我们会遇到这样的需求:为了将数据持久化,将对象转化为字节序列保存在磁盘上,或者反过来,需要使用数据时将保存在磁盘上的文件转为对象。前者称为序列化,后者称为反序列化

会不会有同学这样想,为什么不直接把对象存在磁盘上,而非要把对象转为字节序列呢?

这是因为在系统底层,数据的传输形式是以简单的字节序列形式传递,也就是说,在系统底层,不能识别对象,只能识别字节序列。

在 Java 中,需要类实现 Serializable 标记接口,并借助 ObjectOutputStream  ObjectInputStream 实现序列化与反序列化。

这里把序列化与反序列化的过程封装为工具类 SerializeUtils,代码如下:

public class SerializeUtils {
    public static void writeObject(String filePath, Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream(filePath);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
        oos.close();
    }

    public static <T> T readObject(String filePath) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filePath);
        ObjectInputStream ois = new ObjectInputStream(fis);
        T result = (T) ois.readObject();
        ois.close();
        return result;
    }
}

 SerializeUtils 进行简单的说明:

  • writeObject(String filePath, Object obj) 方法接收两个参数,第一个参数是要写入的文件路径,第二个参数是需要序列化的对象。在方法体内,首先创建了一个 FileOutputStream 对象,文件输出流;再把 FileOutputStream 对象传递给 ObjectOutputStream 的构造器,创建 ObjectOutputStream 对象;接着,调用 oos.writeObject(obj); 把对象写入到文件中;最后关闭输出流。
  • readObject(String filePath) 方法接收一个参数,表示从哪个文件读入。另外,这是一个泛型方法,方便在方法体内进行强制类型转换。在方法体内,首先创建了一个 FileInputStream 对象,文件输入流;再把 FileInputStream 对象传递给 ObjectInputStream 的构造器,创建 ObjectInputStream 对象;接着,调用 ois.readObject() 获取文件中的对象,并强转为替换了泛型类型参数的实际类型。不过,这里的泛型方法在调用时可以利用类型推断,免去了传递的类型来替换泛型类型参数的麻烦。

下面开始代码演示:

Person1 类如下:

public class Person1 {

    private String name;
    private int age;

    public Person1(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

这是一个简单的 Java Bean 类,包含了两个字段,一个构造方法,还有 getter/setter 方法。

声明文件路径为静态变量:

private static String filePath = "./obj.object";

 main() 方法中,开始序列化与反序列化:

SerializeUtils.writeObject(filePath, new Person1("wzc", 32));
Person1 person1 = SerializeUtils.<Person1>readObject(filePath);
System.out.println(person1.getName() + ":" + person1.getAge());

运行后,查看结果:

Exception in thread "main" java.io.NotSerializableException: com.java.advanced.features.io.serialize.serializable.Person1
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.java.advanced.features.io.serialize.SerializeUtils.writeObject(SerializeUtils.java:9)
	at com.java.advanced.features.io.serialize.serializable.SerializableTest.test1(SerializableTest.java:301)
	at com.java.advanced.features.io.serialize.serializable.SerializableTest.main(SerializableTest.java:13)

可以看到程序抛出了异常:NotSerializableException,这是因为 Person1 类没有实现 Serializable 接口导致的。

创建 Person2 类,就是在 Person1 的基础上实现 Serializable 接口。

public class Person2 implements Serializable {
// 省略了与 Person1 类似的代码
}

再次进行测试,先执行序列化的代码:

SerializeUtils.writeObject(filePath, new Person2("wzc", 32));

可以看到项目的根目录会生成 obj.object 文件:

如果我们使用文本编辑器打开 obj.object 文件,可以看到文件里会有一些乱码:

\AC\ED\00sr\00<com.java.advanced.features.io.serialize.serializable.Person2\00\00\00\00\00\00\00\00I\00ageL\00namet\00Ljava/lang/String;xp\00\00\00 t\00\00\00\00\00

这是因为编码问题导致的,我们写入到 obj.object 文件里的是字节序列,而打开文本编辑器使用的解码格式是 UTF-8 或者其它,如果字节序列在解码格式的编码表中找不到对应的字符,就会出现乱码。

我们应该使用打开二进制文件的编辑器来查看。在 Windows 下,可以使⽤ NotePad++打开, 添加 Hex Editor 插件查看对应的⼆进制⽂件。这里我使用的是 Ubuntu 的 GHex 工具来打开:
在这里插入图片描述
再执行反序列化的代码:

Person2 person2 = SerializeUtils.readObject(filePath);
System.out.println(person2.getName() + ":" + person2.getAge());

运行后,打印结果如下:

wzc:32

到这里,对如何使用对实现了 Serializable 接口的类进行序列化和反序列化已经介绍完毕。

下面开始分析序列化流程和反序列化流程:

2.2 流程分析

2.2.1 序列化流程分析

创建 ObjectOutputStream 对象,写入流的头信息

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;
    }

我们看一下 writeStreamHeader() 方法的实现:

protected void writeStreamHeader() throws IOException {
    bout.writeShort(STREAM_MAGIC);
    bout.writeShort(STREAM_VERSION);
}

 STREAM_MAGIC  STREAM_VERSION  ObjectOutputStream 所实现的接口 ObjectStreamConstants 中的常量:

public interface ObjectStreamConstants {

    /**
     * Magic number that is written to the stream header.
     */
    final static short STREAM_MAGIC = (short)0xaced;

    /**
     * Version number that is written to the stream header.
     */
    final static short STREAM_VERSION = 5;
    // 省略了其他常量。。。
}

再来看一下二进制文件的截图,可以对应到写入的信息:

writeObject(Object obj) 方法

public final void writeObject(Object obj) throws IOException {
    if (enableOverride) { // enableOverride 是 false,不会走这个分支的
        writeObjectOverride(obj);
        return;
    }
    try {
        writeObject0(obj, false); // => 代码走到这里
    } catch (IOException ex) {
        if (depth == 0) {
            writeFatalException(ex);
        }
        throw ex;
    }
}

writeObject0(Object obj, boolean unshared) 方法

writeObject0() 方法是 writeObject() 方法的底层实现。

private void writeObject0(Object obj, boolean unshared)
    throws IOException
{
    boolean oldMode = bout.setBlockDataMode(false);
    try {
        // handle previously written and non-replaceable objects
        // 省略与分析无关的代码
        Class<?> cl = obj.getClass();
        ObjectStreamClass desc = ObjectStreamClass.lookup(cl, true);
       // 省略与分析无关的代码
        // 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 {
            if (extendedDebugInfo) {
                throw new NotSerializableException(
                    cl.getName() + "\n" + debugInfoStack.toString());
            } else {
                throw new NotSerializableException(cl.getName());
            }
        }
    } finally {
        bout.setBlockDataMode(oldMode);
    }
}

这个方法里面的

Class<?> cl = obj.getClass();
ObjectStreamClass.lookup(cl, true)

lookup() 方法会去查找并返回给定类的类描述符对象,即 ObjectStreamClass 对象。

lookup() 方法的实现思路就是先查找缓存有没有 ObjectStreamClass 对象,有则返回;没有的话,就去调用 ObjectStreamClass 的构造方法创建 ObjectStreamClass 对象。

我们不用去考虑缓存,因为我们的代码刚跑起来,哪有缓存?

我们直接去看 ObjectStreamClass 的构造方法:

private ObjectStreamClass(final Class<?> cl) {
    this.cl = cl;
    name = cl.getName(); // 类名
    isProxy = Proxy.isProxyClass(cl); // 是否是代理类
    isEnum = Enum.class.isAssignableFrom(cl); // 是否是枚举类
    serializable = Serializable.class.isAssignableFrom(cl); // 是否实现了 Serializable 接口
    externalizable = Externalizable.class.isAssignableFrom(cl); // 是否实现了 Externalizable 接口
    Class<?> superCl = cl.getSuperclass();
    superDesc = (superCl != null) ? lookup(superCl, false) : null;
    localDesc = this;
    if (serializable) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                if (isEnum) {
                    suid = Long.valueOf(0);
                    fields = NO_FIELDS;
                    return null;
                }
                if (cl.isArray()) {
                    fields = NO_FIELDS;
                    return null;
                }
                suid = getDeclaredSUID(cl); // 获取 serialVersionUID 的值
                try {
                    fields = getSerialFields(cl);
                    computeFieldOffsets();
                } catch (InvalidClassException e) {
                    serializeEx = deserializeEx =
                        new ExceptionInfo(e.classname, e.getMessage());
                    fields = NO_FIELDS;
                }
                if (externalizable) {
                    cons = getExternalizableConstructor(cl);
                } else {
                    cons = getSerializableConstructor(cl);
                    // 获取 private void writeObject(ObjectOutputStream oos) 方法的 Method 对象
                    writeObjectMethod = getPrivateMethod(cl, "writeObject",
                        new Class<?>[] { ObjectOutputStream.class },
                        Void.TYPE);
                    // 获取 private void readObject(ObjectInputStream ois) 方法的 Method 对象
                    readObjectMethod = getPrivateMethod(cl, "readObject",
                        new Class<?>[] { ObjectInputStream.class },
                        Void.TYPE);
                    readObjectNoDataMethod = getPrivateMethod(
                        cl, "readObjectNoData", null, Void.TYPE);
                    hasWriteObjectData = (writeObjectMethod != null);
                }
                domains = getProtectionDomains(cons, cl);
                // 获取 private Object writeReplace() 方法的 Method 对象
                writeReplaceMethod = getInheritableMethod(
                    cl, "writeReplace", null, Object.class);
                // 获取 private Object readResolve() 方法的 Method 对象
                readResolveMethod = getInheritableMethod(
                    cl, "readResolve", null, Object.class);
                return null;
            }
        });
    } else {
        suid = Long.valueOf(0);
        fields = NO_FIELDS;
    }
    try {
        fieldRefl = getReflector(fields, this);
    } catch (InvalidClassException ex) {
        // field mismatches impossible when matching local fields vs. self
        throw new InternalError(ex);
    }
    if (deserializeEx == null) {
        if (isEnum) {
            deserializeEx = new ExceptionInfo(name, "enum type");
        } else if (cons == null) {
            deserializeEx = new ExceptionInfo(name, "no valid constructor");
        }
    }
    for (int i = 0; i < fields.length; i++) {
        if (fields[i].getField() == null) {
            defaultSerializeEx = new ExceptionInfo(
                name, "unmatched serializable field(s) declared");
        }
    }
    initialized = true;
}

从上面的注释可以看到,ObjectStreamClass 类就是在序列化过程中要来描述需要序列化的对象的。

回到 writeObject0() 方法里,obj 就是 Person2 对象,它实现了 Serializable 接口,所以obj instanceof Serializable  true,代码会走 writeOrdinaryObject(obj, desc, unshared) 方法。

writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared) 方法

这个方法的参数值为:

  • Object obj, 即 Person2 对象;
  • ObjectStreamClass desc,即对应于 Person2 类的类描述信息封装;
  • boolean unshared,即 false

writeOrdinaryObject() 方法的含义是把普通的可序列化对象写入流中。普通的含义是除了 StringObjectStreamClassObjectStreamClass,数组,枚举常量之外的并实现了 Serializable 接口的类对象。

private void writeOrdinaryObject(Object obj,
                                 ObjectStreamClass desc,
                                 boolean unshared)
    throws IOException
{
    try {
        desc.checkSerialize();
        // 写入表示一个新的对象的字节,final static byte TC_OBJECT = (byte)0x73;
        bout.writeByte(TC_OBJECT); 
        writeClassDesc(desc, false);
        handles.assign(unshared ? null : obj);
        if (desc.isExternalizable() && !desc.isProxy()) {
            writeExternalData((Externalizable) obj);
        } else {
            writeSerialData(obj, desc);
        }
    } finally {
        if (extendedDebugInfo) {
            debugInfoStack.pop();
        }
    }
}

writeClassDesc(desc, false); 方法表示把类的描述信息 ObjectStreamClass 写入流中,这些信息包括表示类描述的信息,类的全类名信息,实现 Serializable  Externalizable 的信息,字段的个数,字段的类型码,字段的名称,非基本类型字段的类型信息。

需要注意的是,writeClassDesc(desc, false) 写入的是类的信息,并不包括对象的信息,即字段的值。

这里不再详述了。

Person2 没有实现 Externalizable 接口,所以 desc.isExternalizable()  false,代码进入 else 分支:writeSerialData(obj, desc);

writeSerialData(Object obj, ObjectStreamClass desc) 方法

这个方法的参数值为:

  • Object obj, 即 Person2 对象;
  • ObjectStreamClass desc,即对应于 Person2 类的类描述信息封装;

writeSerialData() 方法的作用是写入序列化数据,即给定对象的实例化数据,也包括超类的实例化数据。

private void writeSerialData(Object obj, ObjectStreamClass desc)
    throws IOException
{
    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
    for (int i = 0; i < slots.length; i++) {
        ObjectStreamClass slotDesc = slots[i].desc;
        if (slotDesc.hasWriteObjectMethod()) { // 返回 false
            // 省略无关代码
        } else {
            defaultWriteFields(obj, slotDesc); // => 代码走这里
        }
    }
}

defaultWriteFields(Object obj, ObjectStreamClass desc) 方法

这个方法的参数值为:

  • Object obj, 即 Person2 对象;
  • ObjectStreamClass desc,即对应于 Person2 类的类描述信息封装;
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
    throws IOException
{
    Class<?> cl = desc.forClass();
    if (cl != null && obj != null && !cl.isInstance(obj)) {
        throw new ClassCastException();
    }
    desc.checkDefaultSerialize();
    // 把基本类型的字段值,放在 primVals 字节数组里
    int primDataSize = desc.getPrimDataSize();
    if (primVals == null || primVals.length < primDataSize) {
        primVals = new byte[primDataSize];
    }
    desc.getPrimFieldValues(obj, primVals);
    // 写入基本类型的字段值
    bout.write(primVals, 0, primDataSize, false);
    // 再次调用 writeObject0() 方法写入非基本类型的字段值。
    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++) {
        try {
            writeObject0(objVals[i],
                         fields[numPrimFields + i].isUnshared());
        } finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }
    }
}

2.2.2 反序列化流程分析

创建 ObjectInputStream 对象,检查头信息

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);
}

readStreamHeader(); 会检查头信息,查看代码:

protected void readStreamHeader()
    throws IOException, StreamCorruptedException
{
    short s0 = bin.readShort();
    short s1 = bin.readShort();
    if (s0 != STREAM_MAGIC || s1 != STREAM_VERSION) {
        throw new StreamCorruptedException(
            String.format("invalid stream header: %04X%04X", s0, s1));
    }
}

读取头两个 short 值,如果第一个 short 值不等于 STREAM_MAGIC 或第二个short 值不等于STREAM_VERSION,那么就抛出异常:StreamCorruptedException,表示头信息无效。

Object readObject() 方法

public final Object readObject()
    throws IOException, ClassNotFoundException {
    return readObject(Object.class);
}

Object readObject(Class<?> type) 方法

参数的值:

  • Class<?> typeObject.class
private final Object readObject(Class<?> type)
    throws IOException, ClassNotFoundException
{
    if (enableOverride) { // false
        return readObjectOverride();
    }
    if (! (type == Object.class || type == String.class))
        throw new AssertionError("internal error");
    // if nested read, passHandle contains handle of enclosing object
    int outerHandle = passHandle;
    try {
        Object obj = readObject0(type, false); // => 代码会走这里
    	// 省略无关的代码
        return obj;
    } finally {
        passHandle = outerHandle;
        if (closed && depth == 0) {
            clear();
        }
    }
}

Object readObject0(Class<?> type, boolean unshared) 方法

参数的值:

  • Class<?> type  Object.class
  • boolean unshared  false
private Object readObject0(Class<?> type, boolean unshared) throws IOException {
    byte tc;
    // bin.peekByte() 返回流中的字节,读取到的是 TC_OBJECT
    while ((tc = bin.peekByte()) == TC_RESET) {
        bin.readByte();
        handleReset();
    }
    depth++;
    totalObjectRefs++;
    try {
        switch (tc) {
            case TC_OBJECT:
                if (type == String.class) {
                    throw new ClassCastException("Cannot cast an object to java.lang.String");
                }
                return checkResolve(readOrdinaryObject(unshared)); // => 代码走到这里

        }
    } finally {
        depth--;
        bin.setBlockDataMode(oldMode);
    }
}

Object readOrdinaryObject(boolean unshared) 方法

参数的值:

  • boolean unshared  false
private Object readOrdinaryObject(boolean unshared)
    throws IOException
{
    if (bin.readByte() != TC_OBJECT) {
        throw new InternalError();
    }
    // 从流中读取类描述信息 ObjectStreamClass 对象
    ObjectStreamClass desc = readClassDesc(false);
    desc.checkDeserialize();
    Class<?> cl = desc.forClass();
    if (cl == String.class || cl == Class.class
            || cl == ObjectStreamClass.class) {
        throw new InvalidClassException("invalid class descriptor");
    }
    Object obj;
    try {
    // 实例化对象,即 Person2 对象
        obj = desc.isInstantiable() ? desc.newInstance() : null;
    } catch (Exception ex) {
        throw (IOException) new InvalidClassException(
            desc.forClass().getName(),
            "unable to create instance").initCause(ex);
    }
    passHandle = handles.assign(unshared ? unsharedMarker : obj);
    ClassNotFoundException resolveEx = desc.getResolveException();
    if (resolveEx != null) {
        handles.markException(passHandle, resolveEx);
    }
    if (desc.isExternalizable()) {
        readExternalData((Externalizable) obj, desc);
    } else {
        readSerialData(obj, desc); // => 代码走到这里
    }
    handles.finish(passHandle);
    if (obj != null &&
        handles.lookupException(passHandle) == null &&
        desc.hasReadResolveMethod())
    {
        Object rep = desc.invokeReadResolve(obj);
        if (unshared && rep.getClass().isArray()) {
            rep = cloneArray(rep);
        }
        if (rep != obj) {
            // Filter the replacement object
            if (rep != null) {
                if (rep.getClass().isArray()) {
                    filterCheck(rep.getClass(), Array.getLength(rep));
                } else {
                    filterCheck(rep.getClass(), -1);
                }
            }
            handles.setObject(passHandle, obj = rep);
        }
    }
    return obj;
}

readSerialData(Object obj, ObjectStreamClass desc) 方法

这个方法的作用是给实例化的对象字段赋值。

private void readSerialData(Object obj, ObjectStreamClass desc)
    throws IOException
{
    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
    for (int i = 0; i < slots.length; i++) {
        ObjectStreamClass slotDesc = slots[i].desc;
        if (slots[i].hasData) {
            if (obj == null || handles.lookupException(passHandle) != null) {
                defaultReadFields(null, slotDesc); // skip field values
            } else if (slotDesc.hasReadObjectMethod()) {
           		// 省略无关的代码
            } else {
                defaultReadFields(obj, slotDesc); // => 代码走这里
            }
            if (slotDesc.hasWriteObjectData()) {
                skipCustomData();
            } else {
                bin.setBlockDataMode(false);
            }
        } 
    }
}

defaultReadFields(Object obj, ObjectStreamClass desc) 方法

private void defaultReadFields(Object obj, ObjectStreamClass desc)
    throws IOException
{
    Class<?> cl = desc.forClass();
    if (cl != null && obj != null && !cl.isInstance(obj)) {
        throw new ClassCastException();
    }
    // 先设置基本类型字段的值
    int primDataSize = desc.getPrimDataSize();
    if (primVals == null || primVals.length < primDataSize) {
        primVals = new byte[primDataSize];
    }
        bin.readFully(primVals, 0, primDataSize, false);
    if (obj != null) {
        desc.setPrimFieldValues(obj, primVals);
    }
    // 再调用 readObject0 设置非基本类型字段的值。
    int objHandle = passHandle;
    ObjectStreamField[] fields = desc.getFields(false);
    Object[] objVals = new Object[desc.getNumObjFields()];
    int numPrimFields = fields.length - objVals.length;
    for (int i = 0; i < objVals.length; i++) {
        ObjectStreamField f = fields[numPrimFields + i];
        objVals[i] = readObject0(Object.class, f.isUnshared());
        if (f.getField() != null) {
            handles.markDependency(objHandle, passHandle);
        }
    }
    if (obj != null) {
        desc.setObjFieldValues(obj, objVals);
    }
    passHandle = objHandle;
}

2.3 实际开发中使用 Serializable 接口会遇到的问题

类实现了序列化接口,但是存在没有实现序列化接口的成员,运行报错:java.io.NotSerializableException

需要使用 transient 关键字修饰没有实现序列化接口的成员。
值得注意的是,如果没有实现序列化接口的成员变量的值是 null,那么即便不加 transient 关键字也不会报错。

静态变量为什么无法序列化?

静态变量不参与序列化,序列化的是对象的成员字段。

多引用写入问题:同一个引用,多次写入不同的对象内容,但取出的对象是一模一样的

演示代码:

// 序列化
FileOutputStream fos = new FileOutputStream(filePath);
ObjectOutputStream oos = new ObjectOutputStream(fos);
Person2 personWrite = new Person2("wzc", 32);
oos.writeObject(personWrite);
personWrite.setAge(33);
oos.writeObject(personWrite);
oos.close();
// 反序列化
FileInputStream fis = new FileInputStream(filePath);
ObjectInputStream ois = new ObjectInputStream(fis);
Person2 personRead1 = (Person2) ois.readObject();
Person2 personRead2 = (Person2) ois.readObject();
ois.close();
System.out.println("personWrite:" + personWrite);
System.out.println("personRead1:" + personRead1);
System.out.println("personRead2:" + personRead2);
System.out.println("personRead1 == personRead2:" + (personRead1 == personRead2));

打印信息:

personWrite:Person2@692404036{name='wzc', age=33}
personRead1:Person2@1072408673{name='wzc', age=32}
personRead2:Person2@1072408673{name='wzc', age=32}
personRead1 == personRead2:true

第一次使用 personWrite 写入的对象内容是 “wzc”, 32;
第二次使用 personWrite 写入的对象内容是 “wzc”, 33;
但是,反序列化读取到的是一模一样的对象。

解决办法:

  • 在第二次写入之前增加代码 oos.reset();
  • 把第二次写入的代码:oos.writeObject(personWrite); 替换为 oos.writeUnshared(personWrite);
  • 尽量避免多引用写入,使用不同的引用。

父类实现了Serializable,子类没有, 子类是否可以进行序列化?

可以。

子类实现序列化,父类不实现序列化,如何序列化父类的数据?

首先,要给父类添加空参构造器,否则会报错:java.io.InvalidClassException: com.java.advanced.features.io.serialize.serializable.Man; no valid constructor;
其次,让子类负责序列化(反序列化)父类的域。
代码如下:

public class Person7 {
    public String name;
    public int age;
    // 添加了无参构造器
    public Person7() {
    }
    public Person7(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class Man3 extends Person7 implements Serializable  {
    public double salary;

    public Man3(String name, int age, double salary) {
        super(name, age);
        this.salary = salary;
    }

    private void writeObject(ObjectOutputStream oos) throws IOException {
        // 先序列化本类对象
        oos.defaultWriteObject();
        // 再序列化父类的域
        oos.writeObject(name);
        oos.writeInt(age);
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        // 先反序列化本类对象
        ois.defaultReadObject();;
        // 再反序列化父类的域
        name = (String) ois.readObject();
        age = ois.readInt();
    }
}

序列化的时候多一个字段,反序列化的时候少一个字段,或者序列化的时候少一个字段,反序列化的时候多一个字段,会不会报错?

需要显式地声明 serialVersionUID 的值,如为 1L。

private static final long serialVersionUID = 1L;

因为计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException

如果类的 serialVersionUID 是一致的,即便在序列化时的类和反序列化时的类有些不同,该对象仍会被尽最大限度完成反序列化。

writeReplace,writeObject, readResolve,readObject 的执行顺序

writeReplace 先于writeObject, readResolve后于readObject

反序列化打破单例,如何解决?

给单例添加 readResovle() 方法:

public class SingletonSerializeFix implements Serializable {
    private static final long serialVersionUID = 1L;

    private SingletonSerializeFix() {
        //no instance
    }

    public static SingletonSerializeFix getInstance() {
        return SingletonHolder.instance;
    }

    private static class SingletonHolder {
        private static SingletonSerializeFix instance = new SingletonSerializeFix();
    }

    private Object readResolve() {
        return SingletonHolder.instance;
    }
}

3 最后

2.3 部分只是说明了问题以及结论,没有进行详细地说明,代码都有演示在 github 地址里。

代码地址:Github地址

希望这篇文章,能够加深大家对 Serializable 的学习,有助于实际的开发工作。

参考

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。