0
点赞
收藏
分享

微信扫一扫

【详解java序列化&反序列化】

凉夜lrs 2022-03-17 阅读 91

一、什么是序列化和反序列化

把对象转换为字节序列的过程称为对象的序列化;把字节序列恢复为对象的过程称为对象的反序列化。

序列化类必须实现Serializable或Externalizable接口

对象的序列化主要有两种用途: 
        1、把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中; 
       2、在网络上传送对象的字节序列。

二、什么是serialVersionUID

序列化运行时每个序列化类与之关联的一个版本号,用于反序列化时确认流中的类与要序列化的类是否是同一个

如果不是指定的类,serialVersionUID是一个散列计算类的名称、接口、方法和字段使用安全散列算法(SHA)由美国国家标准定义的。如果类没有实现Serializable接口serialVersionUID默认为0

serialVersionUID有两种生成方式:

1、采用这种方式生成的serialVersionUID是1L,例如:
     private staticfinal long serialVersionUID =1L; 
2、采用这种方式生成的serialVersionUID是根据类名,接口名,方法和属性等来生成的,例如:
    private static final long serialVersionUID =4603642343377807741L; 
读取类的serialVersionUID的方式:

ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(User.class);
objectStreamClass.getSerialVersionUID();

显式地定义serialVersionUID有两种用途:

1、 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID
  2、 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID

三、序列化和反序列化

1.ObjectStreamClass类

序列化的类描述符。包含类的名称和序列化ID,用于特定类在java VM加载时通过lookup方法查找或加载

  • Serialization’s descriptor for classes. It contains the name and
  • serialVersionUID of the class. The ObjectStreamClass for a specific class
  • loaded in this Java VM can be found/created using the lookup method.

通过调用lookup方法将重写的writeObject和readObject方法加载到objectStreamClass中
```java
java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>)
if (externalizable) {
    cons = getExternalizableConstructor(cl);
} else {
    cons = getSerializableConstructor(cl);
    writeObjectMethod = getPrivateMethod(cl, "writeObject",
        new Class<?>[] { ObjectOutputStream.class },
        Void.TYPE);
    readObjectMethod = getPrivateMethod(cl, "readObject",
        new Class<?>[] { ObjectInputStream.class },
        Void.TYPE);
    readObjectNoDataMethod = getPrivateMethod(
        cl, "readObjectNoData", null, Void.TYPE);
    hasWriteObjectData = (writeObjectMethod != null);
}
writeReplaceMethod = getInheritableMethod(
    cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
    cl, "readResolve", null, Object.class);

2.序列化:writeObject()

在调用wroteObject()进行序列化之前会先调用ObjectOutputStream的构造函数生成一个ObjectOutputStream对象,构造函数如下:

public ObjectOutputStream(OutputStream out) throws IOException {
    verifySubclass();
    // bout表示底层的字节数据容器
    bout = new BlockDataOutputStream(out);
    handles = new HandleTable(10, (float) 3.00);
    subs = new ReplaceTable(10, (float) 3.00);
    enableOverride = false;
    writeStreamHeader(); // 写入文件头
    bout.setBlockDataMode(true); // flush数据
    if (extendedDebugInfo) {
        debugInfoStack = new DebugTraceInfoStack();
    } else {
        debugInfoStack = null;
    }
}
//往底层字节容器中写入表示序列化的Magic Number以及版本号
protected void writeStreamHeader() throws IOException {
    bout.writeShort(STREAM_MAGIC);
    bout.writeShort(STREAM_VERSION);
}

接下来就是调用writeObject方法将类写入字节流文件中了,底层实现为:

private void writeObject0(Object obj, boolean unshared) throws IOException {
    // ...
    try {
        // ...
        Object orig = obj;
        // 获取要序列化的对象的Class对象
        Class cl = obj.getClass();
        ObjectStreamClass desc;
        for (;;) {
            Class repCl;
            // 创建描述cl的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) {
            // 被序列化对象实现了Serializable接口
            writeOrdinaryObject(obj, desc, unshared);
        } else {
            if (extendedDebugInfo) {
                throw new NotSerializableException(
                    cl.getName() + "\n" + debugInfoStack.toString());
            } else {
                throw new NotSerializableException(cl.getName());
            }
        }
    } finally {
        depth--;
        bout.setBlockDataMode(oldMode);
    }
}
private void writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared) throws IOException {
	//...
	try {
		//验证类是否实现了serialize接口
		desc.checkSerialize();
		//...
		//写入object标志位
		bout.writeByte(TC_OBJECT);
		//写入类元数据,类名,路径,属性,方法,序列号
		writeClassDesc(desc, false);
		handles.assign(unshared ? null : obj);
		//写入被序列化的对象的实例数据
		//Externalizable是继承的Serializable接口,实现了Externalizable就是需要哪个属性就写入哪个属性,默认是不做序列化;需要实现两个方法:writeExternal写入属性方法,readExternal读取属性方法
		if (desc.isExternalizable() && !desc.isProxy()) {
			writeExternalData((Externalizable) obj);
		} else {
			writeSerialData(obj, desc);
		}
	} finally {
		//...
	}
}
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()) {
			//...
			try {
				curContext = new SerialCallbackContext(obj, slotDesc);
				bout.setBlockDataMode(true);
				//如果类有写WriteObject方法则按照writeObject中的方式将数据写入流中
				slotDesc.invokeWriteObject(obj, this);
				bout.setBlockDataMode(false);
				bout.writeByte(TC_ENDBLOCKDATA);
			} finally {
				//...
			}
            //...
		} else {
			defaultWriteFields(obj, slotDesc);
		}
	}
}

3.反序列化:readObject()

反序列化流程就是解析字节码文件中的二进制数据,需要注意的是在做反序列化操作时会先解析数据类型标志,再根据数据类型找到对应的class类比较文件中的serialVersionUID与class中的serialVersionUID是否一致,如果不一致则会报文件反序列化失败错误:java.io.InvalidClassException: com.serializable.User; local class incompatible: stream classdesc serialVersionUID = 4316517312099648406, local class serialVersionUID = 6977261730381530552

4.实现单例模式的序列化和反序列化

增加一个readResolve方法返回单例对象,否则就会返回一个新的对象(违反单例原则)(可以先不写readResolve方法测试下序列化前的对象是否与序列化后对象是同一个对象)

public class MySingleton implements Serializable{
    String name;
    private MySingleton() {
        name = "Singleton";
        System.out.println("MySingleton is creating");
    }
    private static final MySingleton INSTANCE = new MySingleton();

    public static MySingleton getInstance() { return INSTANCE; }

    private Object readResolve() throws ObjectStreamException {
        // instead of the object we're on,
        // return the class variable INSTANCE
        System.out.println("MySingleton read resolve");
        return INSTANCE;
    }
}

底层实现:

java.io.ObjectInputStream
/**
 * Underlying readObject implementation.
 */
private Object readObject0(boolean unshared) throws IOException {
    //...(一些省略代码,这些并不是重点)
    byte tc;
    //读取字节流,获取流类型
    while ((tc = bin.peekByte()) == TC_RESET) {
        bin.readByte();
        handleReset();
    }
    depth++;
    try {
        switch (tc) {
            //...(一些省略代码,这些并不是重点)
            case TC_OBJECT:
                return checkResolve(readOrdinaryObject(unshared));
            //...(一些省略代码,这些并不是重点)
            default:
                throw new StreamCorruptedException(String.format("invalid type code: %02X", tc));
        }
    } finally {
        depth--;
        bin.setBlockDataMode(oldMode);
    }
}
private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }
        //获取类元数据
        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 {
            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);
        }
        //obj对象属性赋值
        if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            readSerialData(obj, desc);
        }
        handles.finish(passHandle);
        //如果类中重写了readResolve方法则调用该方法并赋值给obj返回
        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) {
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
    }

四、static和transient关键字

序列化时,只对对象状态进行了保存,对象方法和类变量等并没有保存,因此序列化并不保存静态变量值,这也就是为什么writeObject方法和readObject 从流中无法拿到用static和transient修饰的变量的原因

private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
    Field[] clFields = cl.getDeclaredFields();
    ArrayList<ObjectStreamField> list = new ArrayList<>();
    //只有属性既不是static又不是transient修饰的才能被序列化
    int mask = Modifier.STATIC | Modifier.TRANSIENT;

    for (int i = 0; i < clFields.length; i++) {
        if ((clFields[i].getModifiers() & mask) == 0) {
            list.add(new ObjectStreamField(clFields[i], false, true));
        }
    }
    int size = list.size();
    return (size == 0) ? NO_FIELDS :
        list.toArray(new ObjectStreamField[size]);
}

五、实现序列化的其它方式

1.是把对象包装成JSON字符串或者xml字符串传输
2.采用谷歌的ProtoBuf
protobuf是google旗下的一款平台无关,语言无关,可扩展的序列化结构数据格式,它定义了一种紧凑得可扩展得二进制协议格式
适合用做数据存储和作为不同应用,不同语言之间相互通信的数据交换格式,只要实现相同的协议格式即同一proto文件被编译成不同的语言版本,加入到各自的工程中去。这样不同语言就可以解析其他语言通过protobuf序列化的数据
3.dubbo RPC 序列化方式:
dubbo序列化:阿里尚未开发成熟的高效java序列化实现,阿里不建议在生产环境使用它
hessian2序列化:hessian是一种跨语言的高效二进制序列化方式。但这里实际不是原生的hessian2序列化,而是阿里修改过的hessianlite,它是dubbo RPC默认启用的序列化方式
json序列化:目前有两种实现,一种是采用的阿里的fastjson库,另一种是采用dubbo中自己实现的简单json库,但其实现都不是特别成熟,而且json这种文本序列化性能一般不如上面两种二进制序列化。
java序列化:主要是采用JDK自带的Java序列化实现,性能很不理想。

六、序列化框架

kryo VS hessian VS Protostuff VSjava

参考文档:深入学习java序列化

举报

相关推荐

0 条评论