1. 程式人生 > >物件迴圈引用與序列化問題

物件迴圈引用與序列化問題

前言

最近遇到一個問題,由於一個物件內部存在相互引用,導致json序列化失敗.比如定義有一個類有

class CircleReference {
    private String param;
    private CircleReference reference;

    public String getParam() {
        return param;
    }

    public void setParam(String param) {
        this.param = param;
    }

    public CircleReference getReference
() { return reference; } public void setReference(CircleReference reference) { this.reference = reference; } public static void main(String[] args){ CircleReference reference = new CircleReference(); reference.setParam("param"); reference.setReference(reference); new
ObjectMapper().writeValueAsString(reference); }

那麼在main方法中會拋StackOverFlowError.原因呢,就是在序列屬性reference的時候,對同一個物件,不停的遞迴,沒有結束,用盡棧空間,最終失敗.

於是,就思考了不同的序列化方案對迴圈引用是如何處理的.

幾種序列化方案對迴圈引用的處理

json

會不同的進行遞迴,直到棧空間溢位,最終序列化失敗.

toString()方法

採用IDE直接生成的toString()方法,不停遞迴,最終棧溢位

Hessian

hessian 序列化後的二進位制檔案中,有專門的一種引 用型別,在序列化的時候,對於每一個Object型別例項,都會放到一個IdentityHashMap

中. 當下次發現有相同的例項時,就會直接寫一個引用即可.反序列化時,亦同,沒有問題.

java 自帶的序列化

同Hessian,也是有相應的引用型別.不會導致問題

protobuf

protobuf在序列化時,我們一般是這樣用的

Message.Builder builder= Message.newBuilder;
builder.setBuilder(builder);
Message message = builder.build();

byte[] data = message.toByteArray();

因為一個builder在經過build()以後,這個變數將不會再變化,因此在builder.set(builder)後,設定的那個builder在內部被build一次,生成一個例項,這個例項的builder屬性為空.而後者在呼叫builder.build()的時候,又會生成另外一個builder,這個builder和剛才設定在builder內部的builder不是同一個builder,因此也不會有迴圈引用的問題.

總結

因此,有這麼幾點規律

  1. 不是所有用於序列化的物件都可以迴圈引用: 如protobuf無何如何,你都做不到迴圈引用.因為,每次生成成的物件是不一樣的.
  2. 支援對迴圈引用序列化方案的需要滿足以下兩點
    1. 序列化後的資料型別有引用型別: 如果沒有引用型別,在序列化時,不能終止,在反序列化時,兩次處理的物件不確定是否是同一個物件.
    2. 支援迴圈引用的序列化方案,應該是存在於同一種語言中:不同的語言,其引用可能會導致不一致,因為有些語言本身可能就不存在迴圈引用問題,比如c語言.