先来看两个例子
示例一:将对象保存成字节数组,再把对象的字节数组还原为对象
示例中用到的Bean


package com.huawei.beans; import java.io.Serializable; public class Student implements Serializable { private String id; private String name; private int age; public Student(String id, String name, int age) { this.id = id; this.name = name; this.age = age; } public String getId() { return id; } public void setId(String id) { this.id = id; } 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 "Student{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", age=" + age + '}'; } }
Main函数类


package com.huawei; import com.huawei.beans.Student; import java.io.*; public class App implements Serializable { public static void main( String[] args) throws IOException, ClassNotFoundException { Student student = new Student("123", "xiaohua", 23); // 将对象保存成字节数组 byte[] bytes = toBytes(student); System.out.println(bytes.length); // 将对象从字节数组恢复 Student student0 = (Student)toObj(bytes); System.out.println(student0); } /** * 把对象保存成字节数组 * @param obj * @return * @throws IOException */ public static byte[] toBytes(Serializable obj) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(obj); return byteArrayOutputStream.toByteArray(); } /** * 从字节数组恢复成对象 * @param bytes * @return * @throws IOException * @throws ClassNotFoundException */ public static Serializable toObj(byte[] bytes) throws IOException, ClassNotFoundException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); Object obj = objectInputStream.readObject(); return (Serializable)obj; } }
输出结果为:
109
Student{id='123', name='xiaohua', age=23}
示例二:将对象保存到文件中,再把对象从文件中还原为对象
Main函数类中增加如下方法


/** * 把对象写入到文件 * @param obj * @param filePath * @throws IOException */ public static void toFile(Serializable obj, String filePath) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream(filePath); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(obj); } /** * 从文件中读取对象 * @param filePath * @return * @throws IOException * @throws ClassNotFoundException */ public static Serializable fromFile(String filePath) throws IOException, ClassNotFoundException { FileInputStream fileInputStream = new FileInputStream(filePath); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); return (Serializable)objectInputStream.readObject(); }
Main函数中增加如下


// 将对象保存到文件 String path = App.class.getClassLoader().getResource("").getPath() + "student.obj"; System.out.println(path); toFile(student, path); // 把文件中的对象字节数组恢复成对象 Student student1 = (Student)fromFile(path); System.out.println(student1);
对象序列化(serialization)与反序列化(deserialization)是将对象转化为便于传输的格式进行发送和接收的两个操作。常见的序列化格式有字节数组、JSON字符串、XML字符串等。
上面演示的就是对象字节序列化与字节反序列化。
Java的序列化就是将对象转化为字节流,以便在进程和网络之间进行传输,而在接收方,需要以相同的方式对字节流进行反序列化,得到传输的对象。
JDK为此提供了序列化和反序列化相关实现,
知识点1:序列化
使用此方法进行序列化的对象必须实现Serialization接口,不然在进行序列化时会抛出NotSerializationException异常
/** * 把对象保存成字节数组 * @param obj * @return * @throws IOException */ public static byte[] toBytes(Serializable obj) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(obj); return byteArrayOutputStream.toByteArray(); }
ObjectOutputStream的writeObject方法将对象序列化后写入一个字节流中(如果obj没有实现Serialization接口,则会抛出java.io.NotSerializableException),而这个字节流就是在初始化ObjectOutputStream对象时传入的字节流,这里使用ByteArrayOutputStream,可以获取到字节流中的字节数组。
知识点2:反序列化
对应序列化,反序列化应该是将字节数组转化为对象。
/** * 从字节数组恢复成对象 * @param bytes * @return * @throws IOException * @throws ClassNotFoundException */ public static Serializable toObj(byte[] bytes) throws IOException, ClassNotFoundException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); Object obj = objectInputStream.readObject(); return (Serializable)obj; }
ByteArrayInputStream将字节数组转化为字节流。而ObjectInputStream的readObject()方法将字节流转化为对象。
知识点3:覆盖serialVersionUID
抛出问题:上面的例子都是在对象序列化之后,立即将结果反序列化,这样肯定是没问题的,但是设想一个场景,如果将对象序列化后把字节数组保存到文件中,然后修改类的定义,比如Student类中增加一个字段,然后再进行反序列化,此时会发生什么事情呢。
步骤1:执行上面代码,首先执行到toFile(student, path)方法
步骤2:修改Student类的定义,增加一个private String grade字段,相应实现set get方法,修改toString方法的实现。
步骤3:执行fromFile(path)方法,可以看到如下错误:
Exception in thread "main" java.io.InvalidClassException: com.huawei.beans.Student; local class incompatible: stream classdesc serialVersionUID = -4096065787823611829, local class serialVersionUID = -8134322492441902371
从错误中可以看到利用流保存到文件中的serialVersionUID与当前类serialVersionUID值不同,导致JVM认为无法转换成Student类。
这就是serialVersionUID字段的作用,如果我们覆盖JVM默认生成的serialVersionUID值,保持前后两个类serialVersionUID值相同,是否可以成功获得student对象呢,答案是可以的。
覆盖serialVersionUID需要满足得条件:static final long serialVersionUID
private final static long serialVersionUID = 1234567L;
注意:也并不是说,只要覆盖了serialVersionUID就可以做到完全兼容,数据完全不会丢失,如果修改已有的变量的类型,如将Student类的name属性改为int型,不用试都知道,反序列化时一定会抛异常。如下:
Exception in thread "main" java.io.InvalidClassException: com.huawei.beans.Student; incompatible types for field name
序列化算法
序列化算法一般会按照步骤做如下事情:
- 将对象实例相关的类元数据输出
- 递归地输出类的超类描述直到不再有超类
- 类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值
- 从上至下递归输出实例的数据
下面给个递归获得类的超类的代码示例:
public static List<Class<?>> getSuperClass(Class<?> calzz){ List<Class<?>> listSuperClass = new ArrayList<Class<?>>(); listSuperClass.add(calzz); Class<?> superclass = calzz.getSuperclass(); while (superclass != null) { if(superclass.getName().equals("java.lang.Object")){ listSuperClass.add(superclass); break; } listSuperClass.add(superclass); superclass = superclass.getSuperclass(); } return listSuperClass; }
序列化以及反序列化的字节流保存了java对象的状态以及相关的描述信息。
序列化到底保存了哪些信息,哪些信息是没有被保存的
到底哪些数据被序列化到了有序字节流中呢?字节流数据包含了non-static和non-transient类型的成员数据、类名、类签名、可序列化的基类以及类中引用的其他对象。
序列化方式
1、 一个对象能够序列化的前提是实现Serializable接口或Externalizable接口,Serializable接口没有方法,更像是个标记。有了这个标记的Class就能被序列化机制处理。
2、 写个程序将对象序列化并输出。ObjectOutputStream能把Object输出成Byte流。
3、 要从持久的文件中读取Bytes重建对象,我们可以使用ObjectInputStream。
序列化与外部化的对比
序列化:
JAVA•优点:内建支持
•优点:易于实现
•缺点:占用空间过大
•缺点:由于额外的开销导致速度变比较慢
外部化
•优点:开销较少(程序员决定存储什么)
•优点:可能的速度提升
•缺点:虚拟机不提供任何帮助,也就是说所有的工作都落到了开发人员的肩上。
序列化用途
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
b)当你想用套接字在网络上传送对象的时候;
c)当你想通过RMI传输对象的时候;
参考文献:https://www.jianshu.com/p/5a85011de960
https://www.jianshu.com/p/edcf7bd2c085?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation