序列化和反序列化
序列化:可以將對象轉化成一個字節序列,便於存儲。
反序列化:將序列化的字節序列還原
優點:可以實現對象的”持久性”, 所謂持久性就是指對象的生命週期不取決於程序。
原生序列化方式
序列化方式一: 實現Serializable接口(隱式序列化)
通過實現Serializable接口,這種是隱式序列化(不需要手動),這種是最簡單的序列化方式,會自動序列化所有非static和 transient關鍵字修飾的成員變量。
class Student implements Serializable{ private String name; private int age; public static int QQ = 1234; private transient String address = "CHINA"; Student(String name, int age ){ this.name = name; this.age = age; } public String toString() { return "name: " + name + "\n" +"age: " + age + "\n" +"QQ: " + QQ + "\n" + "address: " + address; } public void SetAge(int age) { this.age = age; } } public class SerializableDemo { public static void main(String[] args) throws IOException, ClassNotFoundException { //創建可序列化對象 System.out.println("原來的對象:"); Student stu = new Student("Ming", 16); System.out.println(stu); //創建序列化輸出流 ByteArrayOutputStream buff = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(buff); //將序列化對象存入緩衝區 out.writeObject(stu); //修改相關值 Student.QQ = 6666; // 發現打印結果QQ的值被改變 stu.SetAge(18); //發現值沒有被改變 //從緩衝區取回被序列化的對象 ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buff.toByteArray())); Student newStu = (Student) in.readObject(); System.out.println("序列化後取出的對象:"); System.out.println(newStu); } } |
打印結果:
原來的對象: name: Ming age: 16 QQ: 1234 address: CHINA |
序列化後取出的對象:
name: Ming age: 16 QQ: 6666 address: null |
發現address(被transient)和QQ(被static)也沒有被序列化,中途修改QQ的值是爲了以防讀者誤會QQ被序列化了。因爲序列化可以保存對象的狀態,但是QQ的值被改變了,說明沒有被序列化。static成員不屬於對象實例,可能被別的對象修改沒辦法序列化,序列化是序列對象。對於address被反序列化後由於沒有對應的引用,所以爲null。而且Serializable不會調用構造方法。
PS:細心的可能發現序列化很誘人,可以保存對象的初始信息,在以後可以回到這個初始狀態。
序列化方式二:實現Externalizable接口。(顯式序列化)
Externalizable接口繼承自Serializable, 我們在實現該接口時,必須實現writeExternal()和readExternal()方法,而且只能通過手動進行序列化,並且兩個方法是自動調用的,因此,這個序列化過程是可控的,可以自己選擇哪些部分序列化
public class Blip implements Externalizable{ private int i ; private String s; public Blip() {} public Blip(String x, int a) { System.out.println("Blip (String x, int a)"); s = x; i = a; } public String toString() { return s+i; } @Override public void writeExternal(ObjectOutput out) throws IOException { // TODO Auto-generated method stub System.out.println("Blip.writeExternal"); out.writeObject(s); out.writeInt(i); // } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // TODO Auto-generated method stub System.out.println("Blip.readExternal"); s = (String)in.readObject(); i = in.readInt(); } public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { System.out.println("Constructing objects"); Blip b = new Blip("A Stirng", 47); System.out.println(b); ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("F://Demo//file1.txt")); System.out.println("保存對象"); o.writeObject(b); o.close(); //獲得對象 System.out.println("獲取對象"); ObjectInputStream in = new ObjectInputStream(new FileInputStream("F://Demo//file1.txt")); System.out.println("Recovering b"); b = (Blip)in.readObject(); System.out.println(b); } } |
打印結果爲:
Constructing objects Blip (String x, int a) A Stirng47 保存對象 Blip.writeExternal 獲取對象 Recovering b Blip.readExternal A Stirng47 |
當註釋掉writeExternal和readExternal方法後打印結果爲:
Constructing objects Blip (String x, int a) A Stirng47 保存對象 Blip.writeExternal 獲取對象 Recovering b Blip.readExternal null0 |
說明:Externalizable類會調用public的構造函數先初始化對象,在調用所保存的內容將對象還原。假如構造方法不是public則會出現運行時錯誤。
序列化方式三:實現Serializable接口+添加writeObject()和readObject()方法。(顯+隱序列化)
如果想將方式一和方式二的優點都用到的話,可以採用方式三, 先實現Serializable接口,並且添加writeObject()和readObject()方法。注意這裏是添加,不是重寫或者覆蓋。但是添加的這兩個方法必須有相應的格式。
- 方法必須要被private修飾 —–>才能被調用
- 第一行調用默認的defaultRead/WriteObject() —–>隱式序列化非static和transient
- 調用read/writeObject()將獲得的值賦給相應的值 —–>顯式序列化
public class SerDemo implements Serializable{ public transient int age = 23; public String name ; public SerDemo(){ System.out.println("默認構造器。。。"); } public SerDemo(String name) { this.name = name; } private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeInt(age); } private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException { stream.defaultReadObject(); age = stream.readInt(); } public String toString() { return "年齡" + age + " " + name; } public static void main(String[] args) throws IOException, ClassNotFoundException { SerDemo stu = new SerDemo("Ming"); ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bout); out.writeObject(stu); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray())); SerDemo stu1 = (SerDemo) in.readObject(); System.out.println(stu1); } } |
打印結果爲:
年齡23 Ming |
註釋掉stream.writeInt(age)和age= stream.readInt()後:
年齡0 Ming |
方式三結合了顯式和隱式序列化,Ming被正常序列化,由於age被trancient修飾,所以需要顯式序列化。
Json序列化
Json序列化一般會使用jackson包,通過ObjectMapper類來進行一些操作,比如將對象轉化爲byte數組或者將json串轉化爲對象。現在的大多數公司都將json作爲服務器端返回的數據格式。比如調用一個服務器接口,通常的請求爲xxx.json?a=xxx&b=xxx的形式。Json序列化示例代碼如下所示
package serialize; import java.io.IOException; import java.util.ArrayList; import java.util.List; import com.fasterxml.jackson.databind.ObjectMapper; /** * * @author liqqc * */ public class JsonSerialize { public static void main(String[] args) throws IOException { new JsonSerialize().start(); } public void start() throws IOException { User u = new User(); List<User> friends = new ArrayList<>(); u.setUserName("張三"); u.setPassWord("123456"); u.setUserInfo("張三是一個很牛逼的人"); u.setFriends(friends); User f1 = new User(); f1.setUserName("李四"); f1.setPassWord("123456"); f1.setUserInfo("李四是一個很牛逼的人"); User f2 = new User(); f2.setUserName("王五"); f2.setPassWord("123456"); f2.setUserInfo("王五是一個很牛逼的人"); friends.add(f1); friends.add(f2); ObjectMapper mapper = new ObjectMapper(); Long t1 = System.currentTimeMillis(); byte[] writeValueAsBytes = null; for (int i = 0; i < 10; i++) { writeValueAsBytes = mapper.writeValueAsBytes(u); } System.out.println("json serialize: " + (System.currentTimeMillis() - t1) + "ms; 總大小:" + writeValueAsBytes.length); Long t2 = System.currentTimeMillis(); User user = mapper.readValue(writeValueAsBytes, User.class); System.out.println("json deserialize: " + (System.currentTimeMillis() - t2) + "ms; User: " + user); } } |
運行結果
json serialize: 55ms; 總大小:341 json deserialize: 35ms; User: User [userId=null, userName=張三, passWord=123456, userInfo=張三是一個很牛逼的人, friends=[User [userId=null, userName=李四, passWord=123456, userInfo=李四是一個很牛逼的人, friends=null], User [userId=null, userName=王五, passWord=123456, userInfo=王五是一個很牛逼的人, friends=null]]] |
FastJson序列化
fastjson 是由阿里巴巴開發的一個性能很好的Java 語言實現的 Json解析器和生成器。特點:速度快,測試表明fastjson具有極快的性能,超越任其他的java json parser。功能強大,完全支持java bean、集合、Map、日期、Enum,支持範型和自省。無依賴,能夠直接運行在Java SE 5.0以上版本
支持Android。使用時候需引入FastJson第三方jar包。FastJson序列化代碼示例如下所示
package serialize; import java.util.ArrayList; import java.util.List; import com.alibaba.fastjson.JSON; /** * * @author liqqc * */ public class FastJsonSerialize { public static void main(String[] args) { new FastJsonSerialize().start(); } public void start(){ User u = new User(); List<User> friends = new ArrayList<>(); u.setUserName("張三"); u.setPassWord("123456"); u.setUserInfo("張三是一個很牛逼的人"); u.setFriends(friends); User f1 = new User(); f1.setUserName("李四"); f1.setPassWord("123456"); f1.setUserInfo("李四是一個很牛逼的人"); User f2 = new User(); f2.setUserName("王五"); f2.setPassWord("123456"); f2.setUserInfo("王五是一個很牛逼的人"); friends.add(f1); friends.add(f2); //序列化 Long t1 = System.currentTimeMillis(); String text = null; for(int i = 0; i<10; i++) { text = JSON.toJSONString(u); } System.out.println("fastJson serialize: " +(System.currentTimeMillis() - t1) + "ms; 總大小:" + text.getBytes().length); //反序列化 Long t2 = System.currentTimeMillis(); User user = JSON.parseObject(text, User.class); System.out.println("fastJson serialize: " + (System.currentTimeMillis() -t2) + "ms; User: " + user); } } |
運行結果
fastJson serialize: 284ms; 總大小:269 fastJson serialize: 26ms; User: User [userId=null, userName=張三, passWord=123456, userInfo=張三是一個很牛逼的人, friends=[User [userId=null, userName=李四, passWord=123456, userInfo=李四是一個很牛逼的人, friends=null], User [userId=null, userName=王五, passWord=123456, userInfo=王五是一個很牛逼的人, friends=null]]] |
4、ProtoBuff序列化
ProtocolBuffer是一種輕便高效的結構化數據存儲格式,可以用於結構化數據序列化。適合做數據存儲或 RPC 數據交換格式。可用於通訊協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。
優點:跨語言;序列化後數據佔用空間比JSON小,JSON有一定的格式,在數據量上還有可以壓縮的空間。
缺點:它以二進制的方式存儲,無法直接讀取編輯,除非你有 .proto 定義,否則無法直接讀出 Protobuffer的任何內容。
其與thrift的對比:兩者語法類似,都支持版本向後兼容和向前兼容,thrift側重點是構建跨語言的可伸縮的服務,支持的語言多,同時提供了全套RPC解決方案,可以很方便的直接構建服務,不需要做太多其他的工作。 Protobuffer主要是一種序列化機制,在數據序列化上進行性能比較,Protobuffer相對較好。
ProtoBuff序列化對象可以很大程度上將其壓縮,可以大大減少數據傳輸大小,提高系統性能。對於大量數據的緩存,也可以提高緩存中數據存儲量。原始的ProtoBuff需要自己寫.proto文件,通過編譯器將其轉換爲java文件,顯得比較繁瑣。百度研發的jprotobuf框架將Google原始的protobuf進行了封裝,對其進行簡化,僅提供序列化和反序列化方法。其實用上也比較簡潔,通過對JavaBean中的字段進行註解就行,不需要撰寫.proto文件和實用編譯器將其生成.java文件,百度的jprotobuf都替我們做了這些事情了。
一個帶有jprotobuf註解的JavaBean如下所示
package serialize; import java.io.Serializable; import java.util.List; import com.baidu.bjf.remoting.protobuf.FieldType; import com.baidu.bjf.remoting.protobuf.annotation.Protobuf; public class User implements Serializable { private static final long serialVersionUID = -7890663945232864573L; @Protobuf(fieldType = FieldType.INT32, required = false, order = 1) private Integer userId; @Protobuf(fieldType = FieldType.STRING, required = false, order = 2) private String userName; @Protobuf(fieldType = FieldType.STRING, required = false, order = 3) private String passWord; @Protobuf(fieldType = FieldType.STRING, required = false, order = 4) private String userInfo; @Protobuf(fieldType = FieldType.OBJECT, required = false, order = 5) private List<User> friends; public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassWord() { return passWord; } public void setPassWord(String passWord) { this.passWord = passWord; } public String getUserInfo() { return userInfo; } public void setUserInfo(String userInfo) { this.userInfo = userInfo; } public List<User> getFriends() { return friends; } public void setFriends(List<User> friends) { this.friends = friends; } @Override public String toString() { return "User [userId=" + userId + ", userName=" + userName + ", passWord=" + passWord + ", userInfo=" + userInfo + ", friends=" + friends + "]"; } } |
jprotobuf序列化代碼示例如下所示
package serialize; import java.io.IOException; import java.util.ArrayList; import java.util.List; import com.baidu.bjf.remoting.protobuf.Codec; import com.baidu.bjf.remoting.protobuf.ProtobufProxy; /** * * @author liqqc * */ public class ProtoBuffSerialize { public static void main(String[] args) throws IOException { new ProtoBuffSerialize().start(); } public void start() throws IOException { Codec<User> studentClassCodec = ProtobufProxy.create(User.class, false); User u2 = new User(); List<User> friends = new ArrayList<>(); u2.setUserName("張三"); u2.setPassWord("123456"); u2.setUserInfo("張三是一個很牛逼的人"); u2.setFriends(friends); User f1 = new User(); f1.setUserName("李四"); f1.setPassWord("123456"); f1.setUserInfo("李四是一個很牛逼的人"); User f2 = new User(); f2.setUserName("王五"); f2.setPassWord("123456"); f2.setUserInfo("王五是一個很牛逼的人"); friends.add(f1); friends.add(f2); Long stime_jpb_encode = System.currentTimeMillis(); byte[] bytes = null; for(int i = 0; i<10; i++) { bytes = studentClassCodec.encode(u2); } System.out.println("jprotobuf序列化耗時:" + (System.currentTimeMillis() - stime_jpb_encode) + "ms; 總大小:" + bytes.length); Long stime_jpb_decode = System.currentTimeMillis(); User user = studentClassCodec.decode(bytes); Long etime_jpb_decode = System.currentTimeMillis(); System.out.println("jprotobuf反序列化耗時:"+ (etime_jpb_decode-stime_jpb_decode) + "ms; User: " + user); } } |
運行結果
jprotobuf序列化耗時:9ms; 總大小:148 jprotobuf反序列化耗時:0ms; User: User [userId=null, userName=張三, passWord=123456, userInfo=張三是一個很牛逼的人, friends=[User [userId=null, userName=李四, passWord=123456, userInfo=李四是一個很牛逼的人, friends=null], User [userId=null, userName=王五, passWord=123456, userInfo=王五是一個很牛逼的人, friends=null]]] |