Java序列化

什么是序列化

所謂的序列化,即把java對象以二進制形式保存到內(nèi)存、文件或者進行網(wǎng)絡傳輸。從二進制的形式恢復成為java對象,即反序列化。

通過序列化可以將對象持久化,或者從一個地方傳輸?shù)搅硪粋€地方。這方面的應用有RMI,遠程方法調(diào)用。

java中實現(xiàn)序列化有兩種方式,實現(xiàn)Serializable接口或者Externalizable接口。這篇總結(jié)只討論Serializable的情況。

public class SerializeTest implements Serializable{
        private static final long serialVersionUID = 1L;
        public String str;
        public SerializeTest(String str) {
               this.str = str;
        }
        
        public static void main(String[] args) throws IOException, ClassNotFoundException {
               SerializeTest test = new SerializeTest("hello");
               ByteArrayOutputStream oStream = new ByteArrayOutputStream();
               ObjectOutputStream objectOutputStream = new ObjectOutputStream(oStream);
               objectOutputStream.writeObject(test);//序列化
               
               ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(oStream.toByteArray()));
               SerializeTest obj = (SerializeTest) inputStream.readObject();//反序列化
               System.out.println(obj.str);
        }
}

上面的代碼演示了類SerializeTest實現(xiàn)序列化和反序列化的過程。

所有的序列化和反序列化過程都是java默認實現(xiàn)的,你只需要實現(xiàn)接口Serializable,就能得到一個實現(xiàn)了序列化的類。
通過ObjectOutputStream和ObjectInputStream分別將序列化對象輸出或者寫入到某個流當中。流的目的地可以是內(nèi)存字節(jié)數(shù)組(上例)、文件、或者網(wǎng)絡。

下面研究一下序列化過程中的幾個問題:

靜態(tài)變量如何序列化

public class SeriaUtil {
        ByteArrayInputStream bInputStream;
        ByteArrayOutputStream byOutputStream;
        ObjectOutputStream outputStream ;
        ObjectInputStream inputStream;
        
        public void seria(Object test) throws IOException {
            if (byOutputStream == null) {
                byOutputStream = new ByteArrayOutputStream();
            }
            if (outputStream == null) {
                outputStream = new ObjectOutputStream(byOutputStream);
            }
            outputStream.writeObject(test);
//         System.out.println(byOutputStream.toByteArray().length); 
        }
        
        public Object  reSeria() throws IOException, ClassNotFoundException {
                if (bInputStream == null) {
                      bInputStream = new ByteArrayInputStream(byOutputStream.toByteArray());
                }
                if (inputStream == null) {
                    inputStream = new ObjectInputStream(bInputStream);
                }
                Object obj =  inputStream.readObject();
                return obj;
        }
}

public class StaticTest implements Serializable{
    private static final long serialVersionUID = 1L;
    public static int A = 0;
    public static String B = "hello";
    
    public static void main(String[] args) throws IOException, ClassNotFoundException {
            //先序列化類,此時 A=0 B = hello
            SeriaUtil seriaUtil = new SeriaUtil();
            StaticTest test = new StaticTest();
            seriaUtil.seria(test);
            //修改 靜態(tài)變量的值
            StaticTest.A = 1;
            StaticTest.B = "world";
            
            StaticTest obj = (StaticTest) seriaUtil.reSeria();
            //輸出 A = 1 B = world
            System.out.println(obj.A);
            System.out.println(obj.B);
    }
}

上面的代碼說明了,靜態(tài)變量不會被序列化。

序列化StaticTest實例test時,靜態(tài)變量 A=0 B="hello",序列化之后,修改StaticTest類的靜態(tài)變量值,A=1 B="world",
此時反序列化得到之前序列化的實例對象賦給obj,發(fā)現(xiàn)obj的靜態(tài)變量變?yōu)锳=1 B="world",說明靜態(tài)變量并未序列化成功。

事實上,在序列化對象時,會忽略對象中的靜態(tài)變量。很好理解,靜態(tài)變量是屬于類的,而不是某個對象的狀態(tài)。我們序列化面向的是對象,是想要將對象的狀態(tài)保存下來,所以
靜態(tài)變量不會被序列化。反序列化得到的對象中的靜態(tài)變量的值是當前jvm中靜態(tài)變量的值。靜態(tài)變量對于同一個jvm中同一個類加載器加載的類來說,是一樣的。對于同一個靜態(tài)變量,不會存在同一個類的不同實例擁有不同的值。

同一對象序列化兩次,反序列化后得到的兩個對象是否相等

這個問題提到的相等,是指是否為同一對象,即==關(guān)系

在某些情況下,確保這種關(guān)系是很重要的。比如王經(jīng)理和李經(jīng)理擁有同一個辦公室,即存在引用關(guān)系:

public class Manager{
    Room room;
    public Manager(Room r){
        room = r;
    }
}

public class Room{}

public class APP{
    public void main(String args[]){
        Room room = new Room();
        Manager wang = new Manager(room);
        Manager li = new Manager(room);
    }
}

反序列化之后,wang,li,room的這種引用關(guān)系不應該發(fā)生變化。通過代碼驗證一下:

public class ReferenceTest implements Serializable{
        public String a;
        public ReferenceTest() {
            a = "hah";
        }
        public static void main(String[] args) throws Exception {
              System.out.println("構(gòu)造對象********************");
              ReferenceTest test = new ReferenceTest();
              System.out.println("序列化**********************");
              SeriaUtil util = new SeriaUtil();
              util.seria(test);
              util.seria(test);//第二次序列化該對象
              System.out.println("反序列化**********************");
              ReferenceTest obj = (ReferenceTest) util.reSeria();
              ReferenceTest obj1 = (ReferenceTest) util.reSeria();
              System.out.println(obj == obj1);//true
              System.out.println(obj == test);//false
        }
}

上面的例子證明(System.out.println(obj == obj1);//true),同一對象序列化多次之后,反序列化得到的多個對象相等,即內(nèi)存地址一致。

使用同一個ObjectOutputStream對象序列化某個實例時,如果該實例還沒有被序列化過,則序列化,若之前已經(jīng)序列化過,則不再進行序列化,只是做一個標記而已。
所以在反序列化時,可以保持原有的引用關(guān)系。

System.out.println(obj == test);//false 也可以理解,反序列化之后重建了該對象,內(nèi)存地址必然是新分配的,故obj != test

父類沒有實現(xiàn)Serializable,父類中的變量如何序列化

public class SuperTest{
       public String superB;
       public SuperTest() {
           superB = "hehe";
           System.out.println("super 無參構(gòu)造函數(shù)");
       }
       
       public SuperTest(String b){
           System.out.println("super 有參構(gòu)造函數(shù)");
           superB = b;
       }
        public static void main(String[] args) throws IOException, ClassNotFoundException {
              System.out.println("構(gòu)造對象*******************");
              SonTest sonTest = new SonTest("son", "super");
              System.out.println("序列化*********************");
              SeriaUtil  seriaUtil = new SeriaUtil();
              seriaUtil.seria(sonTest);
              System.out.println("反序列化******************");
              SonTest obj = (SonTest) seriaUtil.reSeria();
              System.out.println(obj.sonA);
              System.out.println(obj.superB);
        }
}

class SonTest extends SuperTest  implements Serializable{
     private static final long serialVersionUID = 1L;
     public String sonA;
     public SonTest() {
         System.out.println("son 無參構(gòu)造函數(shù)");
     }
     
     public SonTest(String a, String b) {
         super(b);
         System.out.println("son 有參構(gòu)造函數(shù)");
         sonA = a;
     }
}

輸出:

構(gòu)造對象*******************
super 有參構(gòu)造函數(shù)
son 有參構(gòu)造函數(shù)
序列化*********************
反序列化******************
super 無參構(gòu)造函數(shù)
son
hehe

通過上面的代碼可以看出,父類如果沒有實現(xiàn)serializable,反序列化時會調(diào)用父類的無參構(gòu)造函數(shù)初始化父類當中的變量。

所以,我們可以通過顯示聲明父類的無參構(gòu)造函數(shù),并在其中初始化變量值來控制反序列化后父類變量的值。

transient使用

實現(xiàn)了serializable接口的類在序列化時默認會將所有的非靜態(tài)變量進行序列化。我們可以控制某些字段不被默認的序列化機制序列化。

比如,有些字段是跟當前系統(tǒng)環(huán)境相關(guān)的或者涉及到隱私的,需要保密的。這些字段是不可以被序列化到文件中或者通過網(wǎng)絡傳輸?shù)摹N覀兛梢酝ㄟ^為這些字段聲明
transient關(guān)鍵字,保證其不被序列化。

被關(guān)鍵字transient聲明的變量不會被序列化,反序列化時該變量會被自動填充為null(int 為0)。我們也可以為這些字段實現(xiàn)自己的序列化機制。

public class TransientTest implements Serializable{
       private static final long serialVersionUID = 1L;
        public transient String  str;
        public TransientTest() {
            str = "hello";
        }
        
        private void writeObject(ObjectOutputStream out) throws IOException {
            out.defaultWriteObject();
            String encryption = "key" + str;
            out.writeObject(encryption);
        }

        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            in.defaultReadObject();
           String encryption =  (String) in.readObject();
           str = encryption.substring("key".length(), encryption.length());
        }
        
        public static void main(String[] args) throws IOException, ClassNotFoundException {
              TransientTest test = new TransientTest();
              SeriaUtil util = new SeriaUtil();
              util.seria(test);
              TransientTest reSeria = (TransientTest) util.reSeria();
              System.out.println(reSeria.str);//hello
        }
}

通過實現(xiàn)writeObject和readObject實現(xiàn)自己的序列化機制。上面的代碼模擬了一個加密的序列化過程。

成員變量沒有實現(xiàn)序列化

序列化某個實例時,如果這個實例含有對象類型的成員變量,那么同時會觸發(fā)該變量的序列化機制。這時就要求這個成員變量也實現(xiàn)Serializable接口,如果沒有實現(xiàn)該接口,拋出異常。

public class VariableTest implements Serializable{
    Variable variable ;
    public VariableTest() {
        variable = new Variable();
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
            System.out.println("構(gòu)造對象*************");
            VariableTest variableTest = new VariableTest();
            System.out.println("序列化**************");
            SeriaUtil util = new SeriaUtil();
            //拋出異常:java.io.NotSerializableException
            util.seria(variableTest);
            System.out.println("反序列化****************");
            VariableTest obj = (VariableTest) util.reSeria();
            System.out.println(obj.variable.a);//拋出異常:Exception in thread "main" java.io.NotSerializableException: space.kyu.Variable
    }
}
class Variable {
    public String a;
    public Variable(){
        a = "hehe";
    }
}

單例模式下的序列化

public class SingleTest implements Serializable{
    public static SingleTest instance = new SingleTest();
    private SingleTest(){}
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        SingleTest test = SingleTest.instance;
        SeriaUtil util = new SeriaUtil();
        util.seria(test);
        SingleTest reSeria = (SingleTest) util.reSeria();
        System.out.println(reSeria == SingleTest.instance);//false
    }
}

由上面的代碼可以看出,有兩個SingleTest實例同時存在,通過反序列化破壞了單例模式。反序列化時會開辟新的內(nèi)存空間重新實例化對象,所以單例模式被破壞。

為了解決這種問題,可以實現(xiàn)readResolve()方法。

public class SingleTest implements Serializable{
    public static SingleTest instance = new SingleTest();
    private SingleTest(){}
    private Object readResolve() {
        return SingleTest.instance;
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        SingleTest test = SingleTest.instance;
        SeriaUtil util = new SeriaUtil();
        util.seria(test);
        SingleTest reSeria = (SingleTest) util.reSeria();
        System.out.println(reSeria == SingleTest.instance);//true
    }
}

序列化版本

代碼是在不斷的演化的。1.1版本的類可以讀取1.0版本的序列化文件嗎?這就涉及到序列化的版本管理。

每個序列化版本都有其唯一的ID,他是數(shù)據(jù)域類型和方法簽名的指紋。當類的定義產(chǎn)生變化時,他的指紋也會跟著產(chǎn)生變化,對象流將拒絕讀入具有不同指紋的對象。
如果想保持早期版本的兼容,首先要獲取這個類早期版本的指紋。

我們可以使用 jdk自帶的工具 serialver 獲得這個指紋:serialver Test

staic final long serialVersionUID = -1423859403827594712L

然后將1.1版本中Test類的serialVersionUID常量定義為上面的值,即可序列化老版本的代碼。

如果一個類具有名為serialVersionUID的常量,那么java就不會再主動計算這個值,而是直接將其作為這個版本類的指紋。沒有特殊要求的話,一般都顯示的聲明serialVersionUID:private static final long serialVersionUID = 1L;來保證兼容性

如果對象流中的對象具有在當前版本中沒有的數(shù)據(jù)域,那么對象流會忽略這些數(shù)據(jù);如果當前版本具有對象流中所沒有的數(shù)據(jù)域,那么這些新加的域?qū)⒈辉O為默認值。

序列化與克隆

反序列化重新構(gòu)建對象的機制提供了一種克隆對象的簡便途徑,只要對應的類可序列化即可。

做法很簡單:直接將對象序列化到輸出流當中,然后將其讀回。這樣產(chǎn)生的對象是對現(xiàn)有對象的一個深拷貝。

public class CloneTest implements Serializable, Cloneable {
    public String str;
    public CloneTest(String str) {
        this.str =str;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        SeriaUtil util = new SeriaUtil();
        try {
            util.seria(this);
            CloneTest reSeria = (CloneTest) util.reSeria();
            return reSeria;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } 
        
    }
    public static void main(String[] args) throws CloneNotSupportedException {
        CloneTest test = new CloneTest("hi");
        CloneTest clone = (CloneTest) test.clone();
        System.out.println(clone.str);//hi
        System.out.println(clone == test);//false
    }
}

這樣克隆對象的優(yōu)點是簡單,缺點是比普通的克隆實現(xiàn)要慢的多。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 如果你只知道實現(xiàn) Serializable 接口的對象,可以序列化為本地文件。那你最好再閱讀該篇文章,文章對序列化...
    jiangmo閱讀 563評論 0 2
  • JAVA序列化機制的深入研究 對象序列化的最主要的用處就是在傳遞,和保存對象(object)的時候,保證對象的完整...
    時待吾閱讀 11,203評論 0 24
  • 序列化是什么 信息的傳遞、交換支撐整個互聯(lián)網(wǎng)產(chǎn)業(yè),那么信息的交流的過程中遵循著什么樣的標準。常見的網(wǎng)絡傳輸協(xié)議有 ...
    非典型程序員閱讀 2,389評論 0 5
  • 你在想什么 世界那么大 只存你一念之間 我能裝得下 你夢里的藍天 你親吻過的風 還有你心中 倒映著月光的無盡波瀾 ...
    樹大叔閱讀 252評論 0 0
  • 最大的悲哀莫過于長大,從此,笑不再純粹,哭不再徹底,寧愿像個孩子,不肯看太多的事,聽太多的不是,單純一輩子。開心了...
    邂逅相遇太早閱讀 643評論 0 0

友情鏈接更多精彩內(nèi)容