一、序列化与反序列化
序列化:指堆内存中的java对象数据,通过某种方式把对存储到磁盘文件中,或者传递给其他网络节点(网络传输)。这个过程称为序列化,通常是指将数据结构或对象转化成二进制的过程。
即将对象转化为二进制,用于保存,或者网络传输。
反序列化:把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。也就是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程
与序列化相反,将二进制转化成对象。
二、序列化的作用
① 想把内存中的对象保存到一个文件中或者数据库中时候;
② 想用套接字在网络上传送对象的时候;
③ 想通过RMI传输对象的时候
一些应用场景,涉及到将对象转化成二进制,序列化保证了能够成功读取到保存的对象。 |
三、序列化的实现
要实现对象的序列化,最直接的操作就是实现Serializable接口
使用IO流中的对象流可以实现序列化操作,将对象保存到文件,再读取出来
public class Test { private static final File SAVE_FILE = new File("d:" + File.separator + "a.txt") ; //操作文件 public static void main(String[] args) throws Exception { Person person = new Person(); person.setUserName("测试"); person.setAddress("昌平"); person.setAge(12); //saveObject(person); System.out.println(loadObject()); } public static void saveObject(Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(SAVE_FILE)) ; //序列化构造方法 oos.writeObject(obj) ; //序列化实现方法 oos.close() ; } public static Object loadObject() throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(SAVE_FILE)) ; Object obj = ois.readObject() ; //反序列化 ois.close() ; return obj ; } }
public class Person implements Serializable { private static final long serialVersionUID = 1L; private String userName; private Integer age; private Integer sex; private String address; private String email; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Integer getSex() { return sex; } public void setSex(Integer sex) { this.sex = sex; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { return "Person{" + "userName='" + userName + '\'' + ", age=" + age + ", sex=" + sex + ", address='" + address + '\'' + ", email='" + email + '\'' + '}'; } }
四、序列化ID的作用
可以看到,我们在进行序列化时,加了一个serialVersionUID字段,这便是序列化ID
private static final long serialVersionUID = 1L;
这个序列化ID起着关键的作用,它决定着是否能够成功反序列化!java的序列化机制是通过判断运行时类的serialVersionUID来验证版本一致性的,在进行反序列化时,JVM会把传进来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常。
即序列化ID是为了保证成功进行反序列化
五、默认的序列化ID
当我们一个实体类中没有显式的定义一个名为“serialVersionUID”、类型为long的变量时,Java序列化机制会根据编译时的class自动生成一个serialVersionUID作为序列化版本比较,这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID。譬如,当我们编写一个类时,随着时间的推移,我们因为需求改动,需要在本地类中添加其他的字段,这个时候再反序列化时便会出现serialVersionUID不一致,导致反序列化失败。那么如何解决呢?便是在本地类中添加一个“serialVersionUID”变量,值保持不变,便可以进行序列化和反序列化。
如果没有显示指定serialVersionUID,会自动生成一个。
只有同一次编译生成的class才会生成相同的serialVersionUID
但是如果出现需求变动,Bean类发生改变,则会导致反序列化失败。为了不出现这类的问题,所以我们最好还是显式的指定一个serialVersionUID。
六、序列化其他问题
- 静态变量不会被序列化( static,transient)
- 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口。
- 当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化。
子类序列化时:
如果父类没有实现Serializable接口,没有提供默认构造函数,那么子类的序列化会出错;
如果父类没有实现Serializable接口,提供了默认的构造函数,那么子类可以序列化,父类的成员变量不会被序列化。如果父类实现了Serializable接口,则父类和子类都可以序列化。
七、使用效率更高的序列化框架—Protostuff
其实java的原生序列化方式(通过实现Serialiable接口),效率并不是最高的。
github上有一个分析序列化效率的项目:https://github.com/eishay/jvm-serializers/wiki
其中看的出来性能最优的为google开发的colfer ,但是由于colfer的使用难度太大,而更多的都是使用protostuff序列化框架。适用改框架要引入两个库(core与runtime)。
①github地址:https://github.com/protostuff/protostuff
③如果用Maven,则添加依赖:
<dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-runtime</artifactId> <version>1.7.2</version> </dependency> <dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-core</artifactId> <version>1.7.2</version> </dependency>
测试
import com.dyuproject.protostuff.LinkedBuffer; import com.dyuproject.protostuff.ProtobufIOUtil; import com.dyuproject.protostuff.ProtostuffIOUtil; import com.dyuproject.protostuff.Schema; import com.dyuproject.protostuff.runtime.RuntimeSchema; public class Main { public static void main(String[] args) { User user = new User(); user.setName("旭旭宝宝"); user.setAge(33); Schema<User> schema = RuntimeSchema.getSchema(User.class); // 保存对象,序列化,转化二进制数据 LinkedBuffer buffer = LinkedBuffer.allocate(512); final byte[] protostuff; try { protostuff = ProtobufIOUtil.toByteArray(user, schema, buffer); } finally { buffer.clear(); } // 读取对象,反序列化 User userObject = schema.newMessage(); ProtostuffIOUtil.mergeFrom(protostuff, userObject, schema); System.out.println(userObject); } }
User类,并未实现Serializable接口
public class User { private String name; private int 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; } @Override public String toString() { return "User [name=" + name + ", age=" + age + "]"; } }