java.io.Serializable

一個標示性接口,接口中沒有定義任何的方法或字段,僅用於標示可序列化的語義。
序列化時只對對象的狀態進行保存,而無論對象的方法;序列化前和序列化後的對象的關係爲深拷貝java

  •  Serialization(序列化)將對象轉爲字節的過程:
    首先要建立OutputStream(FileOutputStream、ByteArrayOutputStream等),而後將這些OutputStream封裝在一個ObjectOutputStream中,調用writeObject()方法就能夠將對象序列化,並將其發送給OutputStream。對象的序列化是基於字節,不能使用Reader和Writer等基於字符的層次結構)
  • Deserialization(反序列化)是將字節重建成一個對象的過程:
    將一個InputStream(FileInputstream、ByteArrayInputStream等)封裝在ObjectInputStream內,而後調用readObject()。

序列化 ID

序列化使用一個 hash,該 hash 是根據給定源文件中幾乎全部東西(類路徑、 方法名稱、字段名稱、字段類型、訪問修改方法等) 經過運行 JDK serialver 命令計算出的,反序列化時將該 hash 值與序列化流中的 hash 值相比較,serialVersionUID 相同纔可以被序列化算法

若是serialVersionUID類中沒有指定,JVM將從新計算出serialVersionUID; 若是類幾乎全部東西都相同仍然可以反序列化,不然不能。函數

java.io.InvalidClassException: com.noob.Person; local class incompatible: stream classdesc serialVersionUID = 7763748706987261198, local class serialVersionUID = 1279018472691830503
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1829)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1986)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
	at com.noob.TestSerializable.read(TestSerializable.java:41)
	at com.noob.TestSerializable.main(TestSerializable.java:18)

若是在反序列化前修改了類路徑(或者JVM沒有加載到原有的類)報錯:測試

eg. 修改了Person的類路徑 由 com.noob 改成 com.noob.athis

java.lang.ClassNotFoundException: com.noob.Person
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:677)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1819)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1986)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
	at com.noob.a.TestSerializable.read(TestSerializable.java:41)
	at com.noob.a.TestSerializable.main(TestSerializable.java:18)

反序列化對象類型是序列化對象的父類或自己

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import lombok.Getter;
import lombok.Setter;


public class TestSerializable {

    public static void main(String[] args) {
        try {
            /* 深複製 */
            ByteArrayOutputStream bo = new ByteArrayOutputStream();
            ObjectOutputStream readIn = new ObjectOutputStream(bo);
            A testA = new A("a");
            testA.setB("b");
            testA.setC("c");
            testA.setD("d");

            readIn.writeObject(testA);
            ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
            ObjectInputStream writeOut = new ObjectInputStream(bi);
            B b = B.class.cast(writeOut.readObject()); // 反序列化對象類型是序列化對象的父類或自己
            System.out.println(b);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

@Getter
@Setter
class A extends B implements Serializable {


    public A(String a) {
		super(a);
	}
	private static final long serialVersionUID = 1L;
    private String            d;
    private String a, b, c;

}

@Getter
@Setter
class B implements Serializable {
    private String a, b, c;

    public B(String a) {
        this.a = a;
    }
}

測試發現:加密

  • 在反序列化時,用的還是序列化時的對象類型
  • 沒有無參的構造方法,也能夠(反)序列化
  • 若是A、B 兩個類沒有父子關係,程序異常:

父類的序列化與 Transient 關鍵字

  • 當一個父類實現序列化,子類自動實現序列化,不須要顯式實現Serializable接口
  • 若是父類不實現序列化接口則須要有默認的無參的構造函數!不然子類的序列化會出錯

在父類沒有實現 Serializable 接口時,虛擬機是不會序列化父對象的,而一個 Java 對象的構造必須先有父對象,纔有子對象,反序列化也不例外。因此反序列化時,爲了構造父對象,只能調用父類的無參構造函數做爲默認的父對象。所以當取父對象的變量值時,它的值是調用父類無參構造函數後的值。spa

若是考慮到這種序列化的狀況,在父類無參構造函數中對變量進行初始化,不然的話,父類變量值都是默認聲明的值。.net

Transient 關鍵字的做用是控制變量的序列化,在變量聲明前加上該關鍵字,能夠阻止該變量被序列化到文件中,在被反序列化後,transient 變量的值被設爲初始值
靜態變量、成員方法、聲明transient的變量 都不能被序列化和反序列化!3d

案例分析

eg. 使用 Transient 關鍵字可使得字段不被序列化,那麼還有別的方法嗎?
   根據父類對象序列化的規則,咱們能夠將不須要被序列化的字段抽取出來放到父類中,子類實現 Serializable 接口,父類不實現,根據父類序列化規則,父類的字段數據將不被序列化,造成類圖:
     圖 2. 案例程序類圖
code

  上圖中能夠看出,attr一、attr二、attr三、attr5 都不會被序列化,放在父類中的好處在於當有另一個 Child 類時,attr一、attr二、attr3 依然不會被序列化,不用重複抒寫 transient,代碼簡潔。

序列化容許重構

即便代碼有必定的變化,可是serialVersionUID相同,仍然能夠被反序列化當出現新字段時會被設爲缺省值。

eg. 將原有的Person寫入到文件中。再修改Person類:staS改成非 static,firstName 改成非 transient , 增長屬性addField。(static與transient 有或無 可互相轉換,經測試都不能被正確序列化和反序列化)

package com.noob;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import com.zhongan.fcp.pre.allin.common.utils.JSONUtils;

public class TestSerializable {
    public static void main(String[] args) {
        /* write(); */
        read();

    }

    private static void write() {
       /* try {
            Person ted1 = new Person("firstName", "lastName", 39);

            FileOutputStream fos = new FileOutputStream("tempdata.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(ted1);
            oos.close();
            System.out.println("---serialize obj end.----");
        } catch (Exception ex) {
            ex.printStackTrace();
        }*/
    }

    private static void read() {
        try {
            FileInputStream fis = new FileInputStream("D:/tempdata.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            Person ted2 = (Person) ois.readObject();
            ois.close();
            System.out.println(JSONUtils.toFormatJsonString(ted2));
            // Clean up the file
            new File("tempdata.ser").delete();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person implements java.io.Serializable {
    private static final long serialVersionUID = -5941751315700344441L;
    private/**static**/String    staS             = "xxx"; // static 改成非 static 

    private/**transient**/String firstName;               // transient 改成非 transient 
    private String                lastName;
    private int                   age;
    private String                addField;                // 增長屬性

}

對象引用的序列化

一個類要能被序列化,該類中的全部引用對象也必須是能夠被序列化的。

不然整個序列化操做將會失敗,而且會拋出一個NotSerializableException,除非將不可序列化的引用標記爲transient

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = -5941751315700344441L;

    private static String     staS             = "xxx";                // static 改成非 static 

    private transient String  firstName;                               // transient 改成非 transient 
    private String            lastName;
    private int               age;
    private final Attribute   attribute        = new Attribute();

}

@Data
class Attribute {
    private String lock;
}
java.io.NotSerializableException: com.noob.Attribute
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.noob.TestSerializable.write(TestSerializable.java:26)
	at com.noob.TestSerializable.main(TestSerializable.java:17)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.noob.Attribute
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1539)
	at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2231)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2155)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2013)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
	at com.noob.TestSerializable.read(TestSerializable.java:36)
	at com.noob.TestSerializable.main(TestSerializable.java:18)
Caused by: java.io.NotSerializableException: com.noob.Attribute
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.noob.TestSerializable.write(TestSerializable.java:26)
	at com.noob.TestSerializable.main(TestSerializable.java:17)

序列化對象的引用關係

若是使用序列化機制向文件中寫入了多個對象,在反序列化時,須要按實際寫入的順序讀取。

JAVA的序列化機制採用了一種特殊的算法來保證序列化對象的關係:

全部保存到磁盤中的對象都有一個序列化編號。當程序試圖序列化一個對象時,會先檢查該對象是否已經被序列化過,只有該對象(在本次虛擬機中)從未被序列化,系統纔會將該對象轉換成字節序列並輸出若是對象已經被序列化,程序將直接輸出一個序列化編號,而不是從新序列化。

package com.noob;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

public class TestSerializable {
    public static void main(String[] args) {
        try {
            Person person1 = new Person(1000);
            FileOutputStream fos = new FileOutputStream("D:/tempdata.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(person1);
            oos.flush(); //可選
            System.out.println(String.format("第一次將person1寫入後的長度: %s", new File("D:/tempdata.ser").length()));
            person1.setAge(5555); //修改屬性值
            oos.writeObject(person1);
            System.out.println(String.format("再次將person1寫入後的長度: %s", new File("D:/tempdata.ser").length()));
            
            Person person2 = new Person(1000);
            oos.writeObject(person2);
            oos.close();
            System.out.println(String.format("初始化新person2寫入後的長度:%s ", new File("D:/tempdata.ser").length()));
            
            
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/tempdata.ser"));
            System.out.println("---deserialization obj begin.----");

            Person deserialization_person1 = (Person) ois.readObject();
            System.out.println("deserialization_person1的age: " + deserialization_person1.getAge());
            System.out.println("deserialization_person1的內存: " + deserialization_person1);
            
            Person deserialization_person2 = (Person) ois.readObject();
            System.out.println("deserialization_person2的age: " + deserialization_person2.getAge());
            System.out.println("deserialization_person2的內存: " + deserialization_person2);
            System.out.println(String.format("deserialization_person1與deserialization_person2是否一致:%s ",
                    deserialization_person1 == deserialization_person2));
            
            Person deserialization_person3 = (Person) ois.readObject();
            System.out.println("deserialization_person3的內存: " + deserialization_person3);
            System.out.println(String.format("deserialization_person1與deserialization_person3是否一致:%s ",
                    deserialization_person1 == deserialization_person3));

        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }
}

@Getter
@Setter
@AllArgsConstructor
class Person implements java.io.Serializable {
    /**
 * 
 */
    private static final long serialVersionUID = -5941751315700344441L;

    private int               age;

}

測試結果發現:

  1. 即便修改了age的屬性值,可是deserialization_person2反序列化時仍然是原初始化值。
  2. 第二次寫入對象時文件只增長了 5 字節(存儲新增引用和一些控制信息),而且反序列化時恢復引用關係(地址), 兩個對象是相等的。 

案例分析

流只能被讀取一次!

eg. 若是序列化一個對象,反序列化時屢次readObject,報錯

try {
            Person person = new Person(1000);
            FileOutputStream fos = new FileOutputStream("D:/tempdata.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(person);
            oos.close();
            FileInputStream fis = new FileInputStream("D:/tempdata.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            Person person2 = (Person) ois.readObject();
            Person person3 = (Person) ois.readObject();

            ois.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

eg. 有兩個Teacher對象,它們的Student實例變量都引用了同一個Person對象,並且該Person對象還另一個引用變量引用它。以下圖所示: 

                                            

 這裏有三個對象per、t一、t2,若是都被序列化,會存在這樣一個問題,在序列化t1的時候,會隱式的序列化person對象。在序列化t2的時候,也會隱式的序列化person對象。在序列化per的時候,會顯式的序列化person對象。因此在反序列化的時候,會獲得三個person對象,這樣就會形成t一、t2所引用的person對象不是同一個。顯然,這並不符合圖中所展現的關係,也違背了java序列化的初衷。

package com.noob;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import lombok.AllArgsConstructor;
import lombok.Getter;

public class TestSerializable {
    public static void main(String[] args) {
        write();
        read();

    }

    private static void write() {
        try {
            Person person = new Person(1000);
            Student student = new Student("孫悟空", person);
            Teacher teacher1 = new Teacher("唐僧", student);
            Teacher teacher2 = new Teacher("菩提老祖", student);

            FileOutputStream fos = new FileOutputStream("D:/tempdata.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(person);
            oos.flush(); // 可選
            oos.writeObject(student);
            oos.flush();
            oos.writeObject(teacher1);
            oos.flush();
            oos.writeObject(teacher2);
            oos.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }

    private static void read() {
        try {
            FileInputStream fis = new FileInputStream("D:/tempdata.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            System.out.println("---deserialization obj begin.----");

            Person person = (Person) ois.readObject();
            System.out.println("person的內存: " + person);
            Student student = (Student) ois.readObject();
            System.out.println("student中person內存: " + student.getPerson());
            System.out.println(String.format("student中person與直接反序列化的person是否一致:%s", student.getPerson() == person));
            System.out.println("-------------------------------------------");
            System.out.println("student的內存: " + student);

            Teacher teacher1 = (Teacher) ois.readObject();
            System.out.println("teacher1中student內存: " + teacher1.getStudent());
            System.out
                    .println(String.format("teacher1中student與直接反序列化的student是否一致:%s", teacher1.getStudent() == student));

            Teacher teacher2 = (Teacher) ois.readObject();
            System.out.println("teacher2中student內存: " + teacher2.getStudent());
            System.out
                    .println(String.format("teacher2中student與直接反序列化的student是否一致:%s", teacher2.getStudent() == student));

            ois.close();
            // Clean up the file
            new File("tempdata.ser").delete();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

/**
 * 此處沒有getter/setter 佐證序列和反序列化與此無關
 */
@AllArgsConstructor
class Person implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = -5941751315700344441L;

    private int               age;

}

@Getter
@AllArgsConstructor
class Student implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String            name;
    private Person            person;
}

@Getter
@AllArgsConstructor
class Teacher implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String            name;
    private Student           student;
}

測試結論: 在反序列化後仍舊會保持與序列化前對象間的引用關係!

敏感字段加密

在序列化過程當中,虛擬機會試圖調用對象類裏的 writeObject readObject 方法,進行用戶自定義的序列化和反序列化,若是沒有這樣的方法,則默認調用是 ObjectOutputStream defaultWriteObject 方法以及 ObjectInputStream defaultReadObject 法。

package com.noob;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectInputStream.GetField;
import java.io.ObjectOutputStream;
import java.io.ObjectOutputStream.PutField;

public class Test implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String            password         = "origin_password";

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    private void writeObject(ObjectOutputStream out) {
        try {
            PutField putFields = out.putFields();
            System.out.println("原密碼:" + password);
            password = "encryption";//此處能夠經過公鑰進行加密
            putFields.put("password", password);
            System.out.println("加密後的密碼" + password);
            out.writeFields();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void readObject(ObjectInputStream in) {
        try {
            GetField readFields = in.readFields();
            Object object = readFields.get("password", "");
            System.out.println("要解密的字符串:" + object.toString());
            password = "origin_password";//此處能夠經過私鑰進行解密
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        try {
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:/result.obj"));
            out.writeObject(new Test());
            out.close();

            ObjectInputStream oin = new ObjectInputStream(new FileInputStream("D:/result.obj"));
            Test t = (Test) oin.readObject();
            System.out.println("解密後的字符串:" + t.getPassword());
            oin.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
相關文章
相關標籤/搜索