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

 

posted @ 2020-11-23 14:44  明明1109  阅读(150)  评论(0编辑  收藏  举报