Java对象序列化
1、什么是序列化
Java序列化是指把Java对象转换为字节序列的过程;而反序列化是指把
字节序列
恢复为Java对象的过程。从而实现网络传输、本地存储等需求;
一般Java对象的生命周期比Java虚拟机短,而实际开发中如果需要JVM停止后能够继续持有对象,则需要用到序列化技术将对象持久化到磁盘或数据库;
2、如何实现序列化
将被序列化的类实现Serializable或Externalizable接口即可;在实现Serializable接口的默认情况下,Java会自动将非
transient
(短暂的,临时的)关键字修饰的属性序列化到指定文件中去;
3、Serializable接口示例
1 //JavaBean 2 public class StudentBean implements Serializable{ 3 4 private String code; 5 private String name ; 6 private Integer age ; 7 private Listscores ; 8 // get/set/toString ... 9 10 } 11 public class Score implements Serializable { 12 13 private Integer code ; 14 private String name; 15 private Integer score ; 16 // get/set/toString ... 17 } 18 //SerializableUtils 19 public class SerializableUtils { 20 21 /** 22 * 序列化 23 * 24 * @param obj 实现了Serializable接口的类 25 * @param fileName 文件输出路径 26 * @throws IOException 27 */ 28 public static void serialize(Object obj, String fileName) 29 throws IOException { 30 31 FileOutputStream fos = new FileOutputStream(fileName); 32 BufferedOutputStream bos = new BufferedOutputStream(fos); 33 ObjectOutputStream oos = new ObjectOutputStream(bos); 34 oos.writeObject(obj); 35 oos.close(); 36 } 37 38 /** 39 * 反序列化 40 * 41 * @param fileName 反序列化时读取的文件路径 42 * @return 43 * @throws IOException 44 * @throws ClassNotFoundException 45 */ 46 public static Object deserialize(String fileName) throws IOException, ClassNotFoundException { 47 FileInputStream fis = new FileInputStream(fileName); 48 BufferedInputStream bis = new BufferedInputStream(fis); 49 ObjectInputStream ois = new ObjectInputStream(bis); 50 Object obj = ois.readObject(); 51 ois.close(); 52 return obj; 53 } 54 } 55 // TestClass 56 public class MyTest { 57 58 private StudentBean stu; 59 60 @Before 61 public void before() { 62 stu = new StudentBean(); 63 stu.setAge(18); 64 stu.setCode("101"); 65 stu.setName("张三"); 66 Score s1 = new Score(101, "高等数学", 100); 67 Score s2 = new Score(102, "计算机", 100); 68 Score s3 = new Score(103, "大学物理", 100); 69 List list = new ArrayList (); 70 list.add(s1); 71 list.add(s2); 72 list.add(s3); 73 stu.setScores(list); 74 } 75 76 @Test 77 public void Test() throws IOException, ClassNotFoundException { 78 // 序列化 79 SerializableUtils.serialize(stu, "student.txt"); 80 81 // 反序列化 82 StudentBean student = (StudentBean) SerializableUtils.deserialize("student.txt"); 83 System.out.println(student.getName()); 84 for (Score s : student.getScores()) { 85 System.out.println(s); 86 } 87 } 88 }
4、serialVersionUID
实现Serializable接口之后可选择添加一个serialVersionUID 属性作为该类的一个序列化版本号,该编码可选择自动生成或者自定义编辑,其主要作用是为当前类添加一个版本标识,UID不变则认为类的内容没变。反序列化时会判断UID是否未发生变化,若序列化后修改了此UID值,则会导致反序列化失败 ,抛出InvalidClassException异常;
不指定版本号另一个明显隐患是,不利于jvm间的移植,可能class文件没有更改,但不同jvm可能计算的规则不一样,这样也会导致无法反序列化;
5、Externalizable接口示例
Externalizable(可外部化的)继承自Serializable,须手动实现writeExternal以及readExternal方法;
1 //修改StudentBean 2 public class StudentBean implements Externalizable{ 3 4 private String code; 5 private String name ; 6 private Integer age ; 7 private Listscores ; 8 9 /* 10 * 自定义序列化规则 11 */ 12 @Override 13 public void writeExternal(ObjectOutput out) throws IOException { 14 out.writeObject(code); 15 out.writeObject(name); 16 out.writeInt(age); 17 out.writeObject(scores); 18 } 19 20 /* 21 * 自定义反序列化规则 22 */ 23 @Override 24 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 25 26 code = (String)in.readObject(); 27 name = (String)in.readObject(); 28 age = in.readInt(); 29 scores = (List )in.readObject(); 30 } 31 // get/set/toString 32 }
6、部分属性序列化
6.1 transient关键字
transient关键字,在实现Serializable接口情况下,只需要对不需要序列化的属性使用transient关键字进行修饰即可实现在序列化时忽略该字段;
1 private transient String code
6.2 添加writeObject和readObject方法
添加writeObject和readObject方法,在实现Serializable接口的情况下添加如下两个方法,自定义选择序列化哪些字段,但注意若该字段没有参加序列化,则反序列化时亦不能参与;
defaultWriteObject()会执行默认序列化操作即序列化所有非static、transient属性;transient关键字修饰的属性可以用writeObject()方式实现序列化;
原理:调用ObjectOutputStream中writeObject方法时会检查序列化对象是否实现了自己的writeObject方法,如果是则跳过常规序列化转流程转而调用writerObject方法实现序列化readObject同理;
1 private Integer code ; 2 private transient String name; 3 private Integer score ; 4 5 //序列化 6 private void writeObject(ObjectOutputStream oos) throws IOException { 7 oos.defaultWriteObject(); 8 oos.writeObject(name); 9 } 10 //反序列化 11 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { 12 ois.defaultReadObject(); 13 name = (String)ois.readObject(); 14 }
6.3 修改writeExternal和readExternal方法
修改writeExternal和readExternal方法,原理同上,且序列化与反序列化属性须一致;
1 /* 2 * 自定义序列化规则 3 */ 4 @Override 5 public void writeExternal(ObjectOutput out) throws IOException { 6 //out.writeObject(code); 7 out.writeObject(name); 8 out.writeInt(age); 9 out.writeObject(scores); 10 } 11 12 /* 13 * 自定义反序列化规则 14 */ 15 @Override 16 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 17 18 //code = (String)in.readObject(); 19 name = (String)in.readObject(); 20 age = in.readInt(); 21 scores = (List)in.readObject(); 22 }
7、总结
-
Serializable是标识接口,没有需要实现的方法;Externalizable必须实现writeExternal和readExternal方法;
-
Serializable不需要有空构造,但Externalizable若含有参构造则必须有无参构造函数;
-
Serializable有两种实现方式,默认方式下自动序列化非
static、transient
关键字修饰的属性; -
Serializable默认方式下使用反射实现序列化,性能稍弱,对属性顺序无要求;Externalizable要求属性的序列化与反序列化顺序一致,否则抛出 java.io.OptionalDataException 异常;
-
所有需要网络传输的对象都需要实现序列化接口,通过建议所有的javaBean都实现Serializable接口;
-
同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化;
-
建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级、维护;
-
序列化可以实现深拷贝;