物件迴圈引用與序列化問題
阿新 • • 發佈:2018-12-30
前言
最近遇到一個問題,由於一個物件內部存在相互引用,導致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,因此也不會有迴圈引用的問題.
總結
因此,有這麼幾點規律
- 不是所有用於序列化的物件都可以迴圈引用: 如protobuf無何如何,你都做不到迴圈引用.因為,每次生成成的物件是不一樣的.
- 支援對迴圈引用序列化方案的需要滿足以下兩點
- 序列化後的資料型別有引用型別: 如果沒有引用型別,在序列化時,不能終止,在反序列化時,兩次處理的物件不確定是否是同一個物件.
- 支援迴圈引用的序列化方案,應該是存在於同一種語言中:不同的語言,其引用可能會導致不一致,因為有些語言本身可能就不存在迴圈引用問題,比如c語言.