java_对象序列化、反序列化
1.概念
序列化:将对象转化为字节序列的过程
反序列化:将字节序列转化为对象的过程
用途:
A:将对象转化为字节序列保存在硬盘上,如文件中,如文本中的例子就是将person对象序列化成字节序列,存在person.txt文件中
B:网络传输中一般都是以对象序列化的形式存在,在网络的发送/接收两端进行对象的序列化/反序列化
输入/输出的使用:
一般都是针对内存而言,内存--硬盘--》outputStream 从硬盘--内存--》inputStream
具体使用要根据场景来确定
2.Serializable-example
2.1 无显式的指定UID,采用编译系统自动生成的
Object:
package com.java.baseinfo.knowledge.code.serializable; import java.io.Serializable; public class Person implements Serializable { private int age; private String name; private String sex; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { final StringBuffer sb = new StringBuffer("Person{"); sb.append("age=").append(age); sb.append(", name='").append(name).append('\''); sb.append(", sex='").append(sex).append('\''); sb.append('}'); return sb.toString(); } }
object uid
package com.java.baseinfo.knowledge.code.serializable; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class TestObjSerializeAndDeserialize { public static void main(String[] args) { // 序列化 SerializablePerson(); // 反序列化 //Deserialization(); } private static void SerializablePerson() { Person person = new Person(); person.setAge(10); person.setName("测试"); person.setSex("女"); String path = "src/test/resources/person.txt"; try { FileOutputStream fileOutputStream = new FileOutputStream(new File(path)); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(person); System.out.println("序列化成功"); fileOutputStream.close(); objectOutputStream.close(); } catch (FileNotFoundException e) { System.out.printf("FileNotFoundException====>" + e); } catch (IOException e) { System.out.printf("IOException====>" + e); } } private static Person Deserialization() { try { FileInputStream fileInputStream = new FileInputStream(new File("src/test/resources/person.txt")); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Person person = (Person) objectInputStream.readObject(); System.out.printf("Deserialization.person===>"+person); } catch (FileNotFoundException e) { System.out.printf("FileNotFoundException====>" + e); } catch (IOException e) { System.out.printf("IOException====>" + e); } catch (ClassNotFoundException e) { System.out.printf("ClassNotFoundException====>" + e); } return null; } }
先序列化,才进行反序列化
Deserialization.person===>Person{age=10, name='测试', sex='女'}
给person增加字段,再用原来的序列化后的结果,进行反序列化;
package com.java.baseinfo.knowledge.code.serializable; import java.io.Serializable;
public class Person implements Serializable { private int age; private String name; private String sex; // 增加字段测试序列化 private String addText; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getAddText() { return addText; } public void setAddText(String addText) { this.addText = addText; } @Override public String toString() { final StringBuffer sb = new StringBuffer("Person{"); sb.append("age=").append(age); sb.append(", name='").append(name).append('\''); sb.append(", sex='").append(sex).append('\''); sb.append(", addText='").append(addText).append('\''); sb.append('}'); return sb.toString(); } }
运行结果
IOException====>java.io.InvalidClassException: com.java.baseinfo.knowledge.code.serializable.Person; local class incompatible: stream classdesc serialVersionUID = 6964452789008335213, local class serialVersionUID = -3534890433624150186 Process finished with exit code 0
原因是:之前代码中未显示的指定UID,当Object中新增字段时,编译器又新生成一个UID,于是出现序列化版本不一致的问题;
2.2 显式指定UID
package com.java.baseinfo.knowledge.code.serializable; import java.io.Serializable; public class Person implements Serializable { private static final long serialVersionUID = -3534890433624150186L; private int age; private String name; private String sex; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { final StringBuffer sb = new StringBuffer("Person{"); sb.append("age=").append(age); sb.append(", name='").append(name).append('\''); sb.append(", sex='").append(sex).append('\''); sb.append('}'); return sb.toString(); } }
运行上述main函数, 先进行序列化,然后再对object增加属性,再序列化的结果,进行反序列化
package com.java.baseinfo.knowledge.code.serializable; import java.io.Serializable; public class Person implements Serializable { private static final long serialVersionUID = -3534890433624150186L; private int age; private String name; private String sex; // 增加字段测试序列化 private String addText; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getAddText() { return addText; } public void setAddText(String addText) { this.addText = addText; } @Override public String toString() { final StringBuffer sb = new StringBuffer("Person{"); sb.append("age=").append(age); sb.append(", name='").append(name).append('\''); sb.append(", sex='").append(sex).append('\''); sb.append(", addText='").append(addText).append('\''); sb.append('}'); return sb.toString(); } }
反序列化结果,新增的字段为空值
Deserialization.person===>Person{age=10, name='测试', sex='女', addText='null'} Process finished with exit code 0
显式的指定UID,目前常用到的场景是:当object增加的字段时候,不希望反序列化出现异常,即希望类的不同版本对序列化兼容;
等用到其他场景的时候,再更新。。
3. Externalizable-example
import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class PersonExternal implements Externalizable { private int age; private String name; private String sex; @Override public void writeExternal(ObjectOutput out) throws IOException {
//未实现 } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
//未实现 } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { final StringBuffer sb = new StringBuffer("PersonExternal{"); sb.append("age=").append(age); sb.append(", name='").append(name).append('\''); sb.append(", sex='").append(sex).append('\''); sb.append('}'); return sb.toString(); }
先序列化再反序列化
package com.java.baseinfo.knowledge.code.serializable; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class TetsExternalizable { public static void main(String[] args) { //ExternalizableEnCode(); ExternalizableDecode(); } private static void ExternalizableEnCode() { PersonExternal personExternal = new PersonExternal(); personExternal.setName("测试"); personExternal.setAge(18); personExternal.setSex("女"); File file = new File("src/test/resources/personExternal.txt"); try { FileOutputStream fileOutputStream = new FileOutputStream(file); ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream); outputStream.writeObject(personExternal); System.out.printf("ExternalizableEnCode===>序列化完成"); } catch (FileNotFoundException e) { System.out.printf("ExternalizableEnCode.FileNotFoundException===>" + e); } catch (IOException e) { System.out.printf("ExternalizableEnCode.IOException===>" + e); } } private static void ExternalizableDecode() { try { File file = new File("src/test/resources/personExternal.txt"); FileInputStream fileInputStream = new FileInputStream(file); ObjectInputStream inputStream = new ObjectInputStream(fileInputStream); PersonExternal personExternal = (PersonExternal) inputStream.readObject(); System.out.println("ExternalizableDecode.personExternal====>" + personExternal); } catch (FileNotFoundException e) { System.out.printf("ExternalizableDecode.FileNotFoundException===>" + e); } catch (IOException e) { System.out.printf("ExternalizableDecode.IOException===>" + e); } catch (ClassNotFoundException e) { System.out.printf("ExternalizableDecode.ClassNotFoundException===>" + e); } } }
运行结果
ExternalizableDecode.personExternal====>PersonExternal{age=0, name='null', sex='null'}
此时发现,当时序列化的对象值并未持久化,是因为我们没有重写Externalizable的writeExternal和readExternal方法
重写externalizable方法
package com.java.baseinfo.knowledge.code.serializable; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class PersonExternal implements Externalizable { private int age; private String name; private String sex; @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(age); out.writeObject(name); out.writeObject(sex); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { age = in.readInt(); name = (String) in.readObject(); sex = (String) in.readObject(); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { final StringBuffer sb = new StringBuffer("PersonExternal{"); sb.append("age=").append(age); sb.append(", name='").append(name).append('\''); sb.append(", sex='").append(sex).append('\''); sb.append('}'); return sb.toString(); } }
运行结果
ExternalizableEnCode===>序列化完成ExternalizableDecode.personExternal====>PersonExternal{age=18, name='测试', sex='女'}
结论:
Externalizable反序列化的时候,会调用被序列化的无参构造函数去创建一个新的对象,然后再将被保存的对象的字段分别填充到新的对象中;所以使用Externalizable时候,除了重写write/read方法外,所需要序列化的对象还需要提供一个无参的构造函数;若没有无参构造函数,则会抛异常:java.io.InvalidClassException
eg:
package com.java.baseinfo.knowledge.code.serializable; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class PersonExternal implements Externalizable { private int age; private String name; private String sex; //构造函数 public PersonExternal(int age, String name, String sex) { this.age = age; this.name = name; this.sex = sex; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(age); out.writeObject(name); out.writeObject(sex); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { age = in.readInt(); name = (String) in.readObject(); sex = (String) in.readObject(); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { final StringBuffer sb = new StringBuffer("PersonExternal{"); sb.append("age=").append(age); sb.append(", name='").append(name).append('\''); sb.append(", sex='").append(sex).append('\''); sb.append('}'); return sb.toString(); } }
test main
package com.java.baseinfo.knowledge.code.serializable; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class TetsExternalizable { public static void main(String[] args) { ExternalizableEnCode(); ExternalizableDecode(); } private static void ExternalizableEnCode() { PersonExternal personExternal = new PersonExternal(18,"测试","女"); /* personExternal.setName("测试"); personExternal.setAge(18); personExternal.setSex("女");*/ File file = new File("src/test/resources/personExternal.txt"); try { FileOutputStream fileOutputStream = new FileOutputStream(file); ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream); outputStream.writeObject(personExternal); System.out.printf("ExternalizableEnCode===>序列化完成"); } catch (FileNotFoundException e) { System.out.printf("ExternalizableEnCode.FileNotFoundException===>" + e); } catch (IOException e) { System.out.printf("ExternalizableEnCode.IOException===>" + e); } } private static void ExternalizableDecode() { try { File file = new File("src/test/resources/personExternal.txt"); FileInputStream fileInputStream = new FileInputStream(file); ObjectInputStream inputStream = new ObjectInputStream(fileInputStream); PersonExternal personExternal = (PersonExternal) inputStream.readObject(); System.out.println("ExternalizableDecode.personExternal====>" + personExternal); } catch (FileNotFoundException e) { System.out.printf("ExternalizableDecode.FileNotFoundException===>" + e); } catch (IOException e) { System.out.printf("ExternalizableDecode.IOException===>" + e); } catch (ClassNotFoundException e) { System.out.printf("ExternalizableDecode.ClassNotFoundException===>" + e); } } }
运行结果
ExternalizableEnCode===>序列化完成ExternalizableDecode.IOException===>java.io.InvalidClassException: com.java.baseinfo.knowledge.code.serializable.PersonExternal; no valid constructor
4.transient 关键字
如果对象的属性被赋予transient属性,则该对象在序列化的时候,当前字段不会被序列化;那么再反序列化的时候,该字段会被赋予默认值,如int类型会是0,String为null
eg
public class Person implements Serializable { private static final long serialVersionUID = -3534890433624150186L; private transient int age; private transient String name; private String sex; // 增加字段测试序列化 private String addText;
运行2中的序列化代码,则结果为
序列化成功 反序列化Deserialization.person===>Person{age=0, name='null', sex='女', addText='addText'} Process finished with exit code 0