1. 序列化
-
Java 平台允许我们在内存中创建可复用的 Java 对象,但一般情况下,只有当 JVM 处于运行时,这些对象才可能存在,即这些对象的生命周期不会比 JVM 的生命周期更长。但在现实应用中,就可能要求在 JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java 对象序列化就能够帮助我们实现该功能
-
简单来说序列化是把对象的状态信息转化为可存储或传输的形式过程,也就是把对象转化为字节序列的过程称为对象的序列化反序列化是序列化的逆向过程,把字节数组反序列化为对象,把字节序列恢复为对象的过程成为对象的反序列化
2. Java 中的序列化
- 代码已经上传至 https://github.com/masteryourself/study-distributed.git ,分支是
master
,工程是study-serializable/serializable-jdk
2.1 serialVersionUID
-
Java 的序列化机制是通过判断类的 serialVersionUID 来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是
InvalidCastException
-
如果没有为指定的 class 配置 serialVersionUID,那么 java 编译器会自动给这个 class 进行一个摘要算法,类似于指纹算法,只要这个文件有任何改动,得到的 UID 就会截然不同的,可以保证在这么多类中,这个编号是唯一的
-
serialVersionUID 有两种显示的生成方式
-
一是默认的 1L,比如:private static final long serialVersionUID = 1L; 二是根据类名、接口名、成员方法及属性等来生成一个 64 位的哈希字段
-
当实现 java.io.Serializable 接 口 的 类 没 有 显 式 地 定 义 一 个serialVersionUID 变量时候,Java 序列化机制会根据编译的 Class 自动生成一个 serialVersionUID 作序列化版本比较用,这种情况下,如果Class 文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID 也不会变化的。
-
2.2 序列化使用
- JDK 提 供 了 Java 对 象 的 序 列 化 方 式 , 主 要 通 过 输 出 流
java.io.ObjectOutputStream
和对象输入流java.io.ObjectInputStream
来实现。其中,被序列化的对象需要实现java.io.Serializable
接口
1. User
@Data
public class User implements Serializable {
private Long id;
private String name;
}
2. Serializer
public interface Serializer {
<T> byte[] serialize(T obj) throws Exception;
<T> T deserialize(byte[] data, Class<T> clazz) throws Exception;
}
3. JavaSerializer
public class JavaSerializer implements Serializer {
@Override
public <T> byte[] serialize(T obj) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
return byteArrayOutputStream.toByteArray();
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) throws Exception {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (T) objectInputStream.readObject();
}
}
4. 测试用例
@Test
public void testSerializer() throws Exception {
Serializer serializer = new JavaSerializer();
User user = new User();
user.setId(998L);
user.setName("masteryourself");
byte[] data = serializer.serialize(user);
User deserializeUer = serializer.deserialize(data, User.class);
// User(id=998, name=masteryourself)
System.out.println(deserializeUer);
}
2.3 静态变量序列化
- 在序列化时,并不保存静态变量,因为序列化保存的是对象的状态,而静态变量属于类的状态
1. User
@Data
public class User implements Serializable {
private Long id;
private String name;
public static int num = 100;
}
2. 测试用例
@Test
public void testStaticSerializer() throws Exception {
Serializer serializer = new JavaSerializer();
User user = new User();
user.setId(998L);
user.setName("masteryourself");
byte[] data = serializer.serialize(user);
user.num = 10;
user.setId(997L);
User deserializeUer = serializer.deserialize(data, User.class);
// User(id=998, name=masteryourself)
System.out.println(deserializeUer);
// 10
System.out.println(deserializeUer.num);
}
2.4 父类序列化
-
当一个父类没有实现序列化时,子类继承该父类并且实现了序列化。在反序列化该子类后,是没办法获取到父类的属性值的
-
当一个父类实现序列化,子类自动实现序列化,不需要再显示实现 Serializable 接口
-
当一个对象的实例变量引用了其他对象,序列化该对象时也会把引用对象进行序列化,但是前提是该引用对象必须实现序列化接口
1. SuperUser
@Data
public class SuperUser {
private String sex;
}
2. User
@Data
public class User extends SuperUser implements Serializable {
private Long id;
private String name;
public static int num = 100;
}
3. 测试用例
@Test
public void testParentSerializer() throws Exception {
Serializer serializer = new JavaSerializer();
User user = new User();
user.setId(998L);
user.setName("masteryourself");
user.setSex("man");
byte[] data = serializer.serialize(user);
User deserializeUer = serializer.deserialize(data, User.class);
// null
System.out.println(deserializeUer.getSex());
}
2.5 Transient
- Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null
1. User
@Data
public class User extends SuperUser implements Serializable {
private Long id;
private String name;
public static int num = 100;
private transient int age;
}
2. 测试用例
@Test
public void testTransientSerializer() throws Exception {
Serializer serializer = new JavaSerializer();
User user = new User();
user.setId(998L);
user.setName("masteryourself");
user.setSex("man");
user.setAge(18);
byte[] data = serializer.serialize(user);
User deserializeUer = serializer.deserialize(data, User.class);
// User(id=998, name=masteryourself, age=0)
System.out.println(deserializeUer);
}
2.6 绕开 transient 机制
- writeObject 和 readObject 这两个私有的方法,既不属于 Object、也不是 Serializable,为什么能够在序列化的时候被调用呢? 原因是,ObjectOutputStream 使用了反射来寻找是否声明了这两个方法。因为 ObjectOutputStream 使用 getPrivateMethod,所以这些方法必须声明为 priate。详细见
java.io.ObjectStreamClass#ObjectStreamClass()
1. User
@Data
public class User extends SuperUser implements Serializable {
private Long id;
private String name;
public static int num = 100;
private transient int age;
private void writeObject(ObjectOutputStream objectOutputStream) throws IOException {
objectOutputStream.defaultWriteObject();
objectOutputStream.writeObject(age);
}
private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
objectInputStream.defaultReadObject();
age = (int) objectInputStream.readObject();
}
}
2. 测试用例
@Test
public void testAvoidTransientSerializer() throws Exception {
Serializer serializer = new JavaSerializer();
User user = new User();
user.setId(998L);
user.setName("masteryourself");
user.setSex("man");
user.setAge(18);
byte[] data = serializer.serialize(user);
User deserializeUer = serializer.deserialize(data, User.class);
// User(id=998, name=masteryourself, age=18)
System.out.println(deserializeUer);
}
2.7 序列化实现深克隆
-
在 Java 中存在一个 Cloneable 接口,通过实现这个接口的类都会具备clone 的能力,同时 clone 是在内存中进行,在性能方面会比我们直接通过 new 生成对象要高一些,特别是一些大的对象的生成,性能提升相对比较明显。那么在 Java 领域中,克隆分为深度克隆和浅克隆
-
浅克隆:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。
-
深克隆:被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍
-
序列化实现深克隆原理是把对象序列化输出到一个流中,然后在把对象从序列化流中读取出来,这个对象就不是原来的对象了。
1. User
@Data
public class User extends SuperUser implements Serializable {
private Long id;
private String name;
}
2. Car
@Data
public class Car implements Cloneable, Serializable {
private String colour;
private User user;
@Override
protected Car clone() throws CloneNotSupportedException {
return (Car) super.clone();
}
public Car deepClone() throws Exception {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(this);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(outputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (Car) objectInputStream.readObject();
}
}
3. 测试用例
@Test
public void testDeepCloneSerializer() throws Exception {
User user = new User();
user.setId(998L);
user.setName("masteryourself");
Car car = new Car();
car.setColour("red");
car.setUser(user);
// Car car2 = car.clone();
Car car2 = car.deepClone();
car2.getUser().setName("张三");
// car:Car(colour=red, user=User(id=998, name=masteryourself, age=0))
System.out.println("car:" + car);
// car2:Car(colour=red, user=User(id=998, name=张三, age=0))
System.out.println("car2:" + car2);
}
3. 其他主流序列化技术
3.1 XML
-
代码已经上传至 https://github.com/masteryourself/study-distributed.git ,分支是
master
,工程是study-serializable/serializable-xml
-
XML 序列化的好处在于可读性好,方便阅读和调试。但是序列化以后的字节码文件比较大,而且效率不高,适用于对性能不高,而且 QPS 较低的企业级内部系统之间的数据交换的场景,同时 XML 又具有语言无关性,所以还可以用于异构系统之间的数据交换和协议。比如我们熟知的 Webservice,就是采用 XML 格式对数据进行序列化的
3.1.1 配置文件
1. xml
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.11.1</version>
</dependency>
3.1.2 代码
1. User
@Data
public class User {
private Long id;
private String name;
public static int num = 100;
private transient int age;
}
2. XmlSerializer
public class XmlSerializer implements Serializer {
private XStream xStream = new XStream(new DomDriver());
@Override
public <T> byte[] serialize(T obj) throws Exception {
return xStream.toXML(obj).getBytes();
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) throws Exception {
return (T) xStream.fromXML(new String(data));
}
}
3. 测试用例
@Test
public void testXmlSerializer() throws Exception {
Serializer serializer = new XmlSerializer();
User user = new User();
user.setId(998L);
user.setName("masteryourself");
user.setAge(18);
byte[] data = serializer.serialize(user);
user.num = 10;
// <pers.masteryourself.study.distributed.serializable.xml.User>
// <id>998</id>
// <name>masteryourself</name>
// </pers.masteryourself.study.distributed.serializable.xml.User>
System.out.println(new String(data));
User deserializeUer = serializer.deserialize(data, User.class);
// User(id=998, name=masteryourself, age=0)
System.out.println(deserializeUer);
// 10
System.out.println(deserializeUer.num);
}
3.2 JSON
-
代码已经上传至 https://github.com/masteryourself/study-distributed.git ,分支是
master
,工程是study-serializable/serializable-json
-
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,相对于 XML 来说,JSON 的字节流更小,而且可读性也非常好。现在 JSON
数据格式在企业运用是最普遍的
3.2.1 配置文件
1. xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>
3.2.2 代码
1. User
@Data
public class User {
private Long id;
private String name;
public static int num = 100;
private transient int age;
}
2. FastJsonSerializer
public class FastJsonSerializer implements Serializer {
@Override
public <T> byte[] serialize(T obj) throws Exception {
return JSON.toJSONBytes(obj);
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) throws Exception {
return JSON.parseObject(data, clazz);
}
}
3. 测试用例
@Test
public void testXmlSerializer() throws Exception {
Serializer serializer = new FastJsonSerializer();
User user = new User();
user.setId(998L);
user.setName("masteryourself");
user.setAge(18);
byte[] data = serializer.serialize(user);
user.num = 10;
// {"id":998,"name":"masteryourself"}
System.out.println(new String(data));
User deserializeUer = serializer.deserialize(data, User.class);
// User(id=998, name=masteryourself, age=0)
System.out.println(deserializeUer);
// 10
System.out.println(deserializeUer.num);
}
3.3 Hessian
-
代码已经上传至 https://github.com/masteryourself/study-distributed.git ,分支是
master
,工程是study-serializable/serializable-hessian
-
Hessian 是一个支持跨语言传输的二进制序列化协议,相对于 Java 默认的序列化机制来说,Hessian 具有更好的性能和易用性,而且支持多种
不同的语言 -
Dubbo 采用的就是 Hessian 序列化来实现,只不过 Dubbo 对 Hessian 进行了重构,性能更高
3.3.1 配置文件
1. xml
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.38</version>
</dependency>
3.3.2 代码
1. User
@Data
public class User implements Serializable {
private Long id;
private String name;
}
2. HessianSerializer
public class HessianSerializer implements Serializer {
@Override
public <T> byte[] serialize(T obj) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
HessianOutput hessianOutput = new HessianOutput(byteArrayOutputStream);
hessianOutput.writeObject(obj);
return byteArrayOutputStream.toByteArray();
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) throws Exception {
HessianInput hessianInput = new HessianInput(new ByteArrayInputStream(data));
return (T) hessianInput.readObject(clazz);
}
}
3. 测试用例
@Test
public void testXmlSerializer() throws Exception {
Serializer serializer = new HessianSerializer();
User user = new User();
user.setId(998L);
user.setName("masteryourself");
byte[] data = serializer.serialize(user);
// 19
System.out.println(data.length);
User deserializeUer = serializer.deserialize(data, User.class);
// User(id=998, name=masteryourself)
System.out.println(deserializeUer);
}
3.4 Protobuf
-
代码已经上传至 https://github.com/masteryourself/study-distributed.git ,分支是
master
,工程是study-serializable/serializable-protobuf
-
Protobuf 使用比较广泛,主要是空间开销小和性能比较好,非常适合用于公司内部对性能要求高的 RPC 调用。 另外由于解析性能比较高,序列化以后数据量相对较少,所以也可以应用在对象的持久化场景中。但是要使用 Protobuf 会相对来说麻烦些,因为它有自己的语法,有自己的编译器
3.4.1 配置文件
1. xml
<dependency>
<groupId>com.baidu</groupId>
<artifactId>jprotobuf</artifactId>
<version>2.1.2</version>
</dependency>
3.4.2 代码
1. User
@Data
public class User {
@Protobuf(fieldType = FieldType.INT64)
private Long id;
@Protobuf(fieldType = FieldType.STRING)
private String name;
}
2. ProtobufSerializer
public class ProtobufSerializer implements Serializer {
@Override
public <T> byte[] serialize(T obj) throws Exception {
Codec codec = ProtobufProxy.create(obj.getClass(), false);
return codec.encode(obj);
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) throws Exception {
Codec codec = ProtobufProxy.create(clazz, false);
return (T) codec.decode(data);
}
}
3. 测试用例
@Test
public void testXmlSerializer() throws Exception {
Serializer serializer = new ProtobufSerializer();
User user = new User();
user.setId(998L);
user.setName("masteryourself");
byte[] data = serializer.serialize(user);
// 19
System.out.println(data.length);
User deserializeUer = serializer.deserialize(data, User.class);
// User(id=998, name=masteryourself)
System.out.println(deserializeUer);
}