Java> Java核心卷读书笔记 - 对象序列化
对象序列化(object serialization)是java支持的通用机制,可以将任何对象写出到输出流中,并且也可以回读。简单来说,就是可以将对象数据保存为文件,甚至可以通过网络传输,在这之后或者别的主机上恢复当前保存的数据状态。
序列化方式:Serializable接口和Externalizable接口两种方式。
Serializable接口方式
特点
需要结合ObjectInputStream和ObjectOutputStream使用,ObjectOutputStream用于保存对象,ObjectInputStream用于恢复对象。
对于想要支持序列化的对象,需要实现Serializable接口,这个接口没有任何方法,只是用于标记。
对于不想保存或者无法序列化的对象、属性,使用transient修饰会让被修饰的属性直接绕过序列化。
保存和恢复数据具体的方法
恢复/保存对象 readObject(), writeObject();
恢复/保存基本数据类型(非java对象),例如整型数据:readInt(), writeInt();
readObject和writeObject方法只需要保存和加载它们的数据域,而不需要关心超类数据和任何其他类的信息。
对引用相同对象的处理
如果2个要序列化的不同对象,其属性都指向了同一个对象引用,如何处理?
对此,Java核心卷2的解释是:每个对象都是用一个序列号(serial number)来保存的。也就是说,序列化的每个对象关联的都是一个序列号,而不再是运行时的RAM地址。
序列号
序列号是序列化版本唯一的ID,是数据域类型和方法签名的指纹,通过对类、接口、域类型、和方法签名按规范方式排序,然后将SHA(安全散列算法)应用于这些数据而来。不过,序列号只用到SHA码前8个byte。也就是说,只要类有任何变化,序列号很可能会不一样。
具体算法描述:
保存对象
- 每个对象引用都关联一个序列号;
- 对每个对象,第一次遇到时,保存其输出对象到输出流中;
- 如果某个对象之前已经被保存过,那么只写出“与之前保存过的序列号为x的对象相同”;
恢复对象
通过读回恢复对象,整个过程反过来。
- 对于输入流中的对象,在第一次遇到其序列号时,构建它,并使用流中数据初始化它,然后记录这个顺序号和新对象之间的关联。
- 当遇到“与之前保存过的序列号为x的对象相同”标记时,获取与这个顺序号相关联的对象引用。
示例
现有员工类Employee和经理类Manager,Manager继承自Emolyee,每个Manager拥有一个秘书属于secretary引用属于Employee。
如果要序列化Employee[3]数组对象,其中,Employee[0]和Employee[1]是Employee子类Manager对象,都包含了同一个秘书(Employee对象Employee[2])。
测试类TestObjectStream
public class TestObjectStream { public static void main(String[] args) throws IOException {
// 创建3个Employee及其子类对象, 其中2个Manager的secretary都指向了同一个Employee Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1); Manager carl = new Manager("Carl Cracker", 80000, 1987, 12, 15); carl.setSecretary(harry); Manager tony = new Manager("Tony Tester", 40000, 1990, 3, 15); tony.setSecretary(harry); Employee[] staff = new Employee[3]; staff[0] = harry; staff[1] = carl; staff[2] = tony; // save all employee records to the file employee.dat try(ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.dat"))) { out.writeObject(staff); } try(ObjectInputStream in = new ObjectInputStream(new FileInputStream("employee.dat"))) { Employee[] newStaff = (Employee[]) in.readObject(); newStaff[1].raiseSalary(10); for (Employee e: newStaff) { System.out.println(e); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
Employee类
public class Employee implements Serializable { // 需要序列化的对象, 一定要实现Seriablizable接口 String name; double salary; LocalDate hireDay; Employee(String name, double salary, int year, int month, int day) { this.name = name; this.salary = salary; this.hireDay = LocalDate.of(year, month, day); } public String getName() { return name; } public double getSalary() { return salary; } public LocalDate getHireDay() { return hireDay; } public void raiseSalary(double raiseValue) { this.salary += raiseValue; } @Override public String toString() { return getClass().getSimpleName() + "{" + "name='" + name + '\'' + ", salary=" + salary + ", hireDay=" + hireDay + '}'; } }
Manager类
public class Manager extends Employee{ private Employee secretary; // 秘书 Manager(String name, double salary, int year, int month, int day) { super(name, salary, year, month, day); } public Employee getSecretary() { return secretary; } public void setSecretary(Employee secretary) { this.secretary = secretary; } }
运行结果
控制台输出
Employee{name='Harry Hacker', salary=50000.0, hireDay=1989-10-01} Manager{name='Carl Cracker', salary=80010.0, hireDay=1987-12-15} Manager{name='Tony Tester', salary=40000.0, hireDay=1990-03-15}
Employee.dat (2进制读取,16进制显示)
00000000h: AC ED 00 05 75 72 00 1C 5B 4C 63 68 32 2E 6F 62 ; ..ur..[Lch2.ob 00000010h: 6A 65 63 74 73 74 72 65 61 6D 2E 45 6D 70 6C 6F ; jectstream.Emplo 00000020h: 79 65 65 3B 64 4A B0 C4 18 FD 00 4D 02 00 00 78 ; yee;dJ澳.?M...x 00000030h: 70 00 00 00 03 73 72 00 19 63 68 32 2E 6F 62 6A ; p....sr..ch2.obj 00000040h: 65 63 74 73 74 72 65 61 6D 2E 45 6D 70 6C 6F 79 ; ectstream.Employ 00000050h: 65 65 3F E3 4D FE 56 56 38 26 02 00 03 44 00 06 ; ee?鉓V8&...D.. 00000060h: 73 61 6C 61 72 79 4C 00 07 68 69 72 65 44 61 79 ; salaryL..hireDay 00000070h: 74 00 15 4C 6A 61 76 61 2F 74 69 6D 65 2F 4C 6F ; t..Ljava/time/Lo 00000080h: 63 61 6C 44 61 74 65 3B 4C 00 04 6E 61 6D 65 74 ; calDate;L..namet 00000090h: 00 12 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 ; ..Ljava/lang/Str 000000a0h: 69 6E 67 3B 78 70 40 E8 6A 00 00 00 00 00 73 72 ; ing;xp@鑚.....sr 000000b0h: 00 0D 6A 61 76 61 2E 74 69 6D 65 2E 53 65 72 95 ; ..java.time.Ser? 000000c0h: 5D 84 BA 1B 22 48 B2 0C 00 00 78 70 77 07 03 00 ; ]労."H?..xpw... 000000d0h: 00 07 C5 0A 01 78 74 00 0C 48 61 72 72 79 20 48 ; ..?.xt..Harry H 000000e0h: 61 63 6B 65 72 73 72 00 18 63 68 32 2E 6F 62 6A ; ackersr..ch2.obj 000000f0h: 65 63 74 73 74 72 65 61 6D 2E 4D 61 6E 61 67 65 ; ectstream.Manage 00000100h: 72 48 55 D0 A8 98 9D 05 2D 02 00 01 4C 00 09 73 ; rHU楔槤.-...L..s 00000110h: 65 63 72 65 74 61 72 79 74 00 1B 4C 63 68 32 2F ; ecretaryt..Lch2/ 00000120h: 6F 62 6A 65 63 74 73 74 72 65 61 6D 2F 45 6D 70 ; objectstream/Emp 00000130h: 6C 6F 79 65 65 3B 78 71 00 7E 00 02 40 F3 88 00 ; loyee;xq.~..@髨. 00000140h: 00 00 00 00 73 71 00 7E 00 06 77 07 03 00 00 07 ; ....sq.~..w..... 00000150h: C3 0C 0F 78 74 00 0C 43 61 72 6C 20 43 72 61 63 ; ?.xt..Carl Crac 00000160h: 6B 65 72 71 00 7E 00 05 73 71 00 7E 00 09 40 E3 ; kerq.~..sq.~..@? 00000170h: 88 00 00 00 00 00 73 71 00 7E 00 06 77 07 03 00 ; ?....sq.~..w... 00000180h: 00 07 C6 03 0F 78 74 00 0B 54 6F 6E 79 20 54 65 ; ..?.xt..Tony Te 00000190h: 73 74 65 72 71 00 7E 00 05 ; sterq.~..
Externalizable接口方式
除了让序列化机制保存和恢复对象,类还可以定义它字节的机制,需要实现Externalizable接口。
特性
Externalizable接口存在的意义是什么?
1. 可以自行定义保存哪些属性,不保存哪些属性;
2. 可以不使用transient关键字忽略不必序列化的属性,不用修改原有的属性修饰关键字;
3. 既可以兼容Serializable接口方式的使用方法,又能单独调用readExternal() 和 writeExternal();
代价是,需要修改待保存/恢复的对象所属类,实现Externalizable接口。Externalizable接口包含2个需要实现的方法:
public void readExternal(ObjectInputStream in) throws IOException, ClassNotFoundException; public void writeExternal(ObjectOutputStream out) throws IOException;
示例
保存和恢复Employee对象
Employee类
/** * 员工类 * Externalizable方式实现保存/恢复对象 */ public class Employee implements Externalizable { private static String name; private static double salary; private static LocalDate hireDay; /** * 默认构造函数, 必须要有, 参见Externalizable接口说明: 当用Externalizable重构对象时, 会使用无参数的构造函数, 然后readExternal方法会被调用. 序列化对象通过从一个ObjectInputStream读取并恢复 */ public Employee() { } public Employee(String name, double salary, int year, int month, int day) { this.name = name; this.salary = salary; this.hireDay = LocalDate.of(year, month, day); } /** * 写入对象数据到指定输出流out */ @Override public void writeExternal(ObjectOutput out) throws IOException { System.out.println("calls writeExternal"); out.writeUTF(name); out.writeDouble(salary); out.writeLong(hireDay.toEpochDay()); } /** * 从指定输入流in读取对象数据, 读取顺序一定要和写入顺序一样 */ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = in.readUTF(); salary = in.readDouble(); hireDay = LocalDate.ofEpochDay(in.readLong()); System.out.println("name=" + name +", salary=" + salary + ", hireDay=" + hireDay); } @Override public String toString() { return getClass().getSimpleName() + "{" + "name='" + name + '\'' + ", salary=" + salary + ", hireDay=" + hireDay + '}'; } }
TestExternalizable 测试类
public class TestExternalizable { public static void main(String[] args) throws IOException, ClassNotFoundException { Employee e = new Employee("Carl", 75000, 1987, 10,12); try(ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee2.data"))) { System.out.println("calls out.writeObject"); // e.writeExternal(out); //也可以单独调用 writeExternal out.writeObject(e); } try(ObjectInputStream in = new ObjectInputStream(new FileInputStream("employee2.data"))) { // in.readObject(); //也可以单独调用 readExternal() Employee newEmployee = (Employee)in.readObject(); System.out.println(newEmployee); } } }
运行结果
console输出
calls out.writeObject calls writeExternal name=Carl, salary=75000.0, hireDay=1987-10-12 Employee{name='Carl', salary=75000.0, hireDay=1987-10-12}
文件输出(employee2.data)
00000000h: AC ED 00 05 73 72 00 28 63 68 32 2E 6F 62 6A 65 ; ..sr.(ch2.obje 00000010h: 63 74 73 74 72 65 61 6D 2E 65 78 74 65 72 6E 61 ; ctstream.externa 00000020h: 6C 69 7A 61 62 6C 65 2E 45 6D 70 6C 6F 79 65 65 ; lizable.Employee 00000030h: C1 3B EA 7E C6 B9 B8 67 0C 00 00 78 70 77 16 00 ; ?陗乒竒...xpw.. 00000040h: 04 43 61 72 6C 40 F2 4F 80 00 00 00 00 00 00 00 ; .Carl@騉€....... 00000050h: 00 00 00 19 5D 78 ; ....]x