物件序列化的幾種方式的比較
定義一個待傳輸的物件UserVo:
Java程式碼- public class UserVo{
- private String name;
- private int age;
- private long phone;
- private List<UserVo> friends;
- ……
- }
初始化UserVo的例項src:
Java程式碼- UserVo src = new UserVo();
- src.setName("Yaoming");
- src.setAge(30);
-
src.setPhone(13789878978L);
- UserVo f1 = new UserVo();
- f1.setName("tmac");
- f1.setAge(32);
- f1.setPhone(138999898989L);
- UserVo f2 = new UserVo();
- f2.setName("liuwei");
- f2.setAge(29);
- f2.setPhone(138999899989L);
- List<UserVo> friends = new ArrayList<UserVo>();
- friends.add(f1);
- friends.add(f2);
-
src.setFriends(friends);
JSON格式
採用Google的gson-2.2.2.jar 進行轉義
Java程式碼- Gson gson = new Gson();
- String json = gson.toJson(src);
得到的字串:
Js程式碼- {"name":"Yaoming","age":30,"phone":13789878978,"friends":[{"name":"tmac","age":32,"phone":138999898989},{"name":"liuwei","age":29,"phone":138999899989}]}
位元組數為153
Json的優點:明文結構一目瞭然,可以跨語言,屬性的增加減少對解析端影響較小。缺點:位元組數過多,依賴於不同的第三方類庫。
Object Serialize
UserVo實現Serializalbe介面,提供唯一的版本號:
Java程式碼- public class UserVo implements Serializable{
- private static final long serialVersionUID = -5726374138698742258L;
- private String name;
- private int age;
- private long phone;
- private List<UserVo> friends;
序列化方法:
Java程式碼- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- ObjectOutputStream os = new ObjectOutputStream(bos);
- os.writeObject(src);
- os.flush();
- os.close();
- byte[] b = bos.toByteArray();
- bos.close();
位元組數是238
反序列化:
Java程式碼- ObjectInputStream ois = new ObjectInputStream(fis);
- vo = (UserVo) ois.readObject();
- ois.close();
- fis.close();
Object Serializalbe 優點:java原生支援,不需要提供第三方的類庫,使用比較簡單。缺點:無法跨語言,位元組數佔用比較大,某些情況下對於物件屬性的變化比較敏感。
物件在進行序列化和反序列化的時候,必須實現Serializable介面,但並不強制宣告唯一的serialVersionUID
是否宣告serialVersionUID對於物件序列化的向上向下的相容性有很大的影響。我們來做個測試:
思路一
把UserVo中的serialVersionUID去掉,序列化儲存。反序列化的時候,增加或減少個欄位,看是否成功。
Java程式碼- public class UserVo implements Serializable{
- private String name;
- private int age;
- private long phone;
- private List<UserVo> friends;
儲存到檔案中:
Java程式碼- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- ObjectOutputStream os = new ObjectOutputStream(bos);
- os.writeObject(src);
- os.flush();
- os.close();
- byte[] b = bos.toByteArray();
- bos.close();
- FileOutputStream fos = new FileOutputStream(dataFile);
- fos.write(b);
- fos.close();
增加或者減少欄位後,從檔案中讀出來,反序列化:
Java程式碼- FileInputStream fis = new FileInputStream(dataFile);
- ObjectInputStream ois = new ObjectInputStream(fis);
- vo = (UserVo) ois.readObject();
- ois.close();
- fis.close();
結果:丟擲異常資訊
Java程式碼- Exception in thread "main" java.io.InvalidClassException: serialize.obj.UserVo; local class incompatible: stream classdesc serialVersionUID = 3305402508581390189, local class serialVersionUID = 7174371419787432394
- at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:560)
- at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1582)
- at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1495)
- at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1731)
- at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)
- at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)
- at serialize.obj.ObjectSerialize.read(ObjectSerialize.java:74)
- at serialize.obj.ObjectSerialize.main(ObjectSerialize.java:27)
思路二
eclipse指定生成一個serialVersionUID,序列化儲存,修改欄位後反序列化
略去程式碼
結果:反序列化成功
結論
如果沒有明確指定serialVersionUID,序列化的時候會根據欄位和特定的演算法生成一個serialVersionUID,當屬性有變化時這個id發生了變化,所以反序列化的時候就會失敗。丟擲“本地classd的唯一id和流中class的唯一id不匹配”。
jdk文件關於serialVersionUID的描述:
寫道 如果可序列化類未顯式宣告 serialVersionUID,則序列化執行時將基於該類的各個方面計算該類的預設 serialVersionUID 值,如“Java(TM) 物件序列化規範”中所述。不過,強烈建議 所有可序列化類都顯式宣告 serialVersionUID 值,原因是計算預設的 serialVersionUID 對類的詳細資訊具有較高的敏感性,根據編譯器實現的不同可能千差萬別,這樣在反序列化過程中可能會導致意外的 InvalidClassException。因此,為保證 serialVersionUID 值跨不同 java 編譯器實現的一致性,序列化類必須宣告一個明確的 serialVersionUID 值。還強烈建議使用 private 修飾符顯示宣告 serialVersionUID(如果可能),原因是這種宣告僅應用於直接宣告類 -- serialVersionUID 欄位作為繼承成員沒有用處。陣列類不能宣告一個明確的 serialVersionUID,因此它們總是具有預設的計算值,但是陣列類沒有匹配 serialVersionUID 值的要求。Google ProtoBuf
protocol buffers 是google內部得一種傳輸協議,目前專案已經開源(http://code.google.com/p/protobuf/)。它定義了一種緊湊得可擴充套件得二進位制協議格式,適合網路傳輸,並且針對多個語言有不同得版本可供選擇。
以protobuf-2.5.0rc1為例,準備工作:
下載原始碼,解壓,編譯,安裝
Shell程式碼- tar zxvf protobuf-2.5.0rc1.tar.gz
- ./configure
- ./make
- ./make install
測試:
Shell程式碼- MacBook-Air:~ ming$ protoc --version
- libprotoc 2.5.0
安裝成功!進入原始碼得java目錄,用mvn工具編譯生成所需得jar包,protobuf-java-2.5.0rc1.jar
1、編寫.proto檔案,命名UserVo.proto
Text程式碼- package serialize;
- option java_package = "serialize";
- option java_outer_classname="UserVoProtos";
- message UserVo{
- optional string name = 1;
- optional int32 age = 2;
- optional int64 phone = 3;
- repeated serialize.UserVo friends = 4;
- }
2、在命令列利用protoc 工具生成builder類
Shell程式碼- protoc -IPATH=.proto檔案所在得目錄 --java_out=java檔案的輸出路徑 .proto的名稱
得到UserVoProtos類
3、編寫序列化程式碼
Java程式碼- UserVoProtos.UserVo.Builder builder = UserVoProtos.UserVo.newBuilder();
- builder.setName("Yaoming");
- builder.setAge(30);
- builder.setPhone(13789878978L);
- UserVoProtos.UserVo.Builder builder1 = UserVoProtos.UserVo.newBuilder();
- builder1.setName("tmac");
- builder1.setAge(32);
- builder1.setPhone(138999898989L);
- UserVoProtos.UserVo.Builder builder2 = UserVoProtos.UserVo.newBuilder();
- builder2.setName("liuwei");
- builder2.setAge(29);
- builder2.setPhone(138999899989L);
- builder.addFriends(builder1);
- builder.addFriends(builder2);
- UserVoProtos.UserVo vo = builder.build();
- byte[] v = vo.toByteArray();
位元組數53
4、反序列化
Java程式碼- UserVoProtos.UserVo uvo = UserVoProtos.UserVo.parseFrom(dstb);
- System.out.println(uvo.getFriends(0).getName());
google protobuf 優點:位元組數很小,適合網路傳輸節省io,跨語言 。缺點:需要依賴於工具生成程式碼。
工作機制
proto檔案是對資料的一個描述,包括欄位名稱,型別,位元組中的位置。protoc工具讀取proto檔案生成對應builder程式碼的類庫。protoc xxxxx --java_out=xxxxxx 生成java類庫。builder類根據自己的演算法把資料序列化成位元組流,或者把位元組流根據反射的原理反序列化成物件。官方的示例:https://developers.google.com/protocol-buffers/docs/javatutorial。
proto檔案中的欄位型別和java中的對應關係:
詳見:https://developers.google.com/protocol-buffers/docs/proto
.proto Type | java Type | c++ Type |
double | double | double |
float | float | float |
int32 | int | int32 |
int64 | long | int64 |
uint32 | int | uint32 |
unint64 | long | uint64 |
sint32 | int | int32 |
sint64 | long | int64 |
fixed32 | int | uint32 |
fixed64 | long | uint64 |
sfixed32 | int | int32 |
sfixed64 | long | int64 |
bool | boolean | bool |
string | String | string |
bytes | byte | string |