參考:http://blog.csdn.net/jiangwei0910410003/article/details/18989711/
http://www.cnblogs.com/guanghuiqq/archive/2012/07/18/2597036.htmlhtml
Java序列化是指把Java對象轉換爲字節序列的過程;而Java反序列化是指把字節序列恢復爲Java對象的過程。即序列化就是將Java Object轉成byte[];反序列化就是將byte[]轉成Java Object。java
將須要序列化的類實現Serializable接口就能夠了,Serializable接口中沒有任何方法,能夠理解爲一個標記,即代表這個類能夠序列化。算法
它的writeObject(Object obj)方法能夠對參數指定的obj對象進行序列化,把獲得的字節序列寫到一個目標輸出流中。數據庫
它的readObject()方法從輸入流中讀取字節序列,再把它們反序列化成爲一個對象,並將其返回。編程
說明:爲了正確讀取數據,完成反序列化,必須保證向對象輸出流寫對象的順序與從對象輸入流中讀對象的順序一致。json
Student類:api
import java.io.Serializable; public class Student implements Serializable { private String name; private char sex; private int year; public Student() {} public Student(String name, char sex, int year) { this.name = name; this.sex = sex; this.year = year; } public void setName(String name) { this.name = name; } public void setSex(char sex) { this.sex = sex; } public void setYear(int year) { this.year = year; } public String getName() { return this.name; } public char getSex() { return this.sex; } public int getYear() { return this.year; } }
測試類:數組
import java.io.*; public class UseStudent { public static void main(String[] args) { Student st = new Student("Tom", 'M', 20); File file = new File("D:\\home\\IO\\student.txt"); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } try { // Student對象序列化過程 FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(st); oos.flush(); oos.close(); fos.close(); // Student對象反序列化過程 FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis); Student st1 = (Student) ois.readObject(); System.out.println("name = " + st1.getName()); System.out.println("sex = " + st1.getSex()); System.out.println("year = " + st1.getYear()); ois.close(); fis.close(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
生成的序列化文件:
緩存
每一個文件都是如下面這兩個字節的「魔幻數字」開始的:AC ED
後面緊跟着對象序列化格式的版本號,目前是:00 05
73:TC_OBJECT聲明這是一個新的對象安全
72:TC_CLASSDESC聲明這裏開始一個新class
00 07:class名字的長度是7字節
53 74 75 64 65 6E 74:類名(ASCII碼:Student,注意:咱們這裏並無定義Student所屬的包,正常狀況下這裏應該是包名.Student
)
84 07 FA B3 1D A2 24 08: SerialVersionUID
02:標記號,該值聲明該對象支持序列化
00 03:該類所包含的域的個數爲3
43::域類型,表明C,表示Char類型
00 03:域名字的長度,爲3
73 65 78:sex屬性的名稱
49:域類型,表明I,表示Int類型
00 04:域名字的長度,爲4
79 65 61 72:year屬性的名稱
4C:域類型,表明L,表示String類型
00 04:域名字的長度,爲4
6E 61 6D 65:name屬性的名稱
74 00 12:實例域的類名及其長度
4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B:Ljava/lang/String;
78:TC_ENDBLOCKDATA,對象塊結束標誌
70:TC_NULL,說明沒有其餘超類的標誌
00 4D:M
00 00 00 14:20
74 00 03:字符串類型及其長度
54 6F 6D:Tom
當存儲一個對象時,這個對象所屬的類也必須存儲。但不是類的全部的class信息都被存儲,只包含一些能惟一性肯定類的描述。這個類的描述包含:
指紋是經過對類、超類、接口、域類型和方法簽名按照規範方式排序,而後將安全散列算法(SHA)應用於這些數據而得到的。在讀入一個對象時,會拿其指紋與它所屬的類的當前指紋進行比對,若是它們不匹配,那麼就說明這個類的定義在該對象被寫出以後發生過變化,所以會產生一個異常。
總結:
簡單來講,Java的序列化機制是經過在運行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體(類)的serialVersionUID進行比較,若是相同就認爲是一致的,能夠進行反序列化,不然就會出現序列化版本不一致的異常(InvalidCastException)。
private static final long serialVersionUID = xxxxL;
當實現java.io.Serializable接口的實體(類)沒有顯式地定義一個名爲serialVersionUID(類型爲long的變量)時,Java序列化機制會根據編譯的class(根據類名、接口名、成員方法及屬性等來生成一個64位的哈希字段,理論上是一一映射的關係,也就是惟一的)自動生成一個serialVersionUID做序列化版本,這種狀況下,若是class文件(類名,方法名等)沒有發生變化,就算再編譯屢次,serialVersionUID也不會變化的。若是咱們不但願經過編譯來強制劃分軟件版本,就須要顯式地定義一個名爲serialVersionUID,類型爲long的變量,不修改這個變量值的序列化實體均可以相互進行序列化和反序列化。
一、問題一:假設有A端和B端,若是2處的serialVersionUID不一致,會產生什麼錯誤呢?
答:會報錯版本不一致(解答能夠見下面的測試)。
二、問題二:假設2處serialVersionUID一致,若是A端增長一個字段,B端不變,(或者A端不變,B端減小一個字段)會是什麼狀況呢?
答:序列化,反序列化正常,A端增長的字段丟失(被B端忽略)。
三、問題三:假設2處serialVersionUID一致,若是B段增長一個字段,A端不變,(或者A端減小一個字段,B端不變)會是什麼狀況呢?
答:序列化,反序列化正常,B端新增長的int字段被賦予了默認值0。
強烈建議全部可序列化類都顯式聲明 serialVersionUID 值,緣由是計算默認的 serialVersionUID 對類的詳細信息具備較高的敏感性,根據編譯器實現的不一樣可能千差萬別,這樣在反序列化過程當中可能會致使意外的 InvalidClassException。還強烈建議使用 private 修飾符顯示聲明 serialVersionUID(若是可能),緣由是:serialVersionUID 字段不該該對子類起做用。數組類不能聲明一個明確的 serialVersionUID,所以它們老是具備默認的計算值,可是數組類沒有匹配 serialVersionUID 值的要求。
有兩個包,模擬A端和B端,分別有本身的Student實體類。測試在A端序列化,在B端反序列化。程序結構:
A包:
package A; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class SerialA { public static void main(String[] args) { Student student = new Student(1, "song"); System.out.println("Object SeStudent" + student); try { File file = new File("D:\\home\\IO\\student.txt"); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(student); oos.flush(); oos.close(); fos.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
package A; import java.io.Serializable; public class Student implements Serializable { private static final long serialVersionUID = 6977402643848374753L; int id; String name; public Student(int id, String name) { this.id = id; this.name = name; } public String toString() { return "DATA: " + id + " " + name; } }
B包:
package B; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream; public class SerialB { public static void main(String[] args) { Student student; try { File file = new File("D:\\home\\IO\\student.txt"); FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis); student = (Student) ois.readObject(); ois.close(); fis.close(); System.out.println("Object DeStudent" + student); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
package B; import java.io.Serializable; public class Student implements Serializable { private static final long serialVersionUID = 6977402643848374753L; int id; String name; public Student(int id, String name) { this.id = id; this.name = name; } public String toString() { return "DATA: " + id + " " + name; } }
咱們在A端序列化,B端反序列化,獲得:
也就是利用不一樣包下的類,是不能完成反序列化的
修改測試程序結構以下,程序代碼不變:
咱們在A端序列化,B端反序列化,獲得:
咱們首先在A端進行序列化,而後在B端反序列化以前,將Student.java中的serialVersionUID改成:
private static final long serialVersionUID = 1L;
,
獲得:
java.io.InvalidClassException: model.Student; local class incompatible: stream classdesc serialVersionUID = 6977402643848374753, local class serialVersionUID = 1 at java.io.ObjectStreamClass.initNonProxy(Unknown Source) at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source) at java.io.ObjectInputStream.readClassDesc(Unknown Source) at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source) at java.io.ObjectInputStream.readObject0(Unknown Source) at java.io.ObjectInputStream.readObject(Unknown Source) at B.SerialB.main(SerialB.java:17)
咱們首先在A端進行序列化,而後在B端反序列化以前,將Student.java改成:
獲得:
Object DeStudentDATA: 1
咱們首先在A端進行序列化,而後在B端反序列化以前,將Student.java改成:
獲得:
Object DeStudentDATA: 1 song 0
反序列化還原後的對象地址與原來的的地址不一樣。即直接將對象序列化到輸出流中,而後將其讀回,這樣產生的新對象是對現有對象的一個深拷貝或者深複製。可是注意:使用這種方式深複製雖然比較方便,可是他比顯式的構建新對象並賦值數據域慢不少。
標記某些數據域是不能夠序列化的。當某個字段被聲明爲transient後,默認序列化機制就會忽略該字段。transient關鍵字只能修飾變量,而不能修飾方法和類。注意,本地變量是不能被transient關鍵字修飾的。變量若是是用戶自定義類變量,則該類須要實現Serializable接口。
序列化會忽略靜態變量,即序列化不保存靜態變量的狀態。靜態成員屬於類級別的,因此不能序列化。即 序列化的是對象的狀態不是類的狀態。這裏的不能序列化的意思,是序列化信息中不包含這個靜態成員域。因爲該值是JVM加載該類時分配的值,因此讀取該對象時這個值依然存在。
若是僅僅只是讓某個類實現Serializable接口,而沒有其它任何處理的話,就是使用默認序列化機制。使用默認機制,在序列化對象時,不只會序列化當前對象自己,還會對該對象引用的其它對象也進行序列化,一樣地,這些其它對象引用的另外對象也將被序列化,以此類推。因此,若是一個對象包含的成員變量是容器類對象,而這些容器所含有的元素也是容器類對象,那麼這個序列化的過程就會較複雜,開銷也較大,因此咱們介紹兩種比較快速的序列化方式。
實體類:
import java.io.Serializable; public class Student implements Serializable { private String name; private char sex; private int year; public Student() {} public Student(String name, char sex, int year) { this.name = name; this.sex = sex; this.year = year; } public void setName(String name) { this.name = name; } public void setSex(char sex) { this.sex = sex; } public void setYear(int year) { this.year = year; } public String getName() { return this.name; } public char getSex() { return this.sex; } public int getYear() { return this.year; } }
序列化類:
import com.alibaba.fastjson.JSON; import com.dyuproject.protostuff.LinkedBuffer; import com.dyuproject.protostuff.ProtobufIOUtil; import com.dyuproject.protostuff.ProtostuffIOUtil; import com.dyuproject.protostuff.runtime.RuntimeSchema; import com.test.serializationTest.model.Student; import java.io.*; public class Test { private Student st = new Student("Tom", 'M', 20); public void testSerializable() { ByteArrayOutputStream os = new ByteArrayOutputStream(); try { //序列化 ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(st); oos.flush(); oos.close();//只是爲了方便簡潔的作個例子,真實編程要放到finally下 System.out.println("Serializable字節數:"+os.toByteArray().length); System.out.println("Serializable字節流:"+os); //反序列化 ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(os.toByteArray())); Student st2 = (Student) ois.readObject(); System.out.println("Serializable反序列化:"+st2.getName()+st2.getSex()+st2.getYear()); ois.close(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public void testJSON() { String json = JSON.toJSONString(st); System.out.println("JSON字節數::"+json.getBytes().length); System.out.println("JSON字符串:"+json); Student st2 = JSON.parseObject(json,Student.class); System.out.println("JSON反序列化:"+st2.getName()+st2.getSex()+st2.getYear()); } public void testProtostuff() { RuntimeSchema<Student> schema = RuntimeSchema.createFrom(Student.class); //序列化 byte[] data = ProtobufIOUtil.toByteArray(st, schema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE)); System.out.println("Protostuff字節數::"+data.length); System.out.println("Protostuff字節流:"+data); //反序列化 Student st2 = schema.newMessage(); ProtostuffIOUtil.mergeFrom(data,st2, schema); System.out.println("Protostuff反序列化:"+st2.getName()+st2.getSex()+st2.getYear()); }
測試類:
public class SerializationTestApplication { public static void main(String[] args) { Test test = new Test(); test.testSerializable(); test.testJSON(); test.testProtostuff(); } }
輸出:
Serializable字節數:114 Serializable字節流:�� sr (com.test.serializationTest.model.Student=;S^��Y C sexI yearL namet Ljava/lang/String;xp M t Tom Serializable反序列化:TomM20 JSON字節數::34 JSON字符串:{"name":"Tom","sex":"M","year":20} JSON反序列化:TomM20 Protostuff字節數::9 Protostuff字節流:[B@2b9ed6da Protostuff反序列化:TomM20
Java的序列化:
優勢:使用方便,可序列化全部類,不須要提供第三方的類庫。
缺點:速度慢,沒法跨語言,字節數佔用比較大(徹底利用反射實現序列化和反序列化,而且保存了關於類的一些信息)。
Protostuff是一個開源的、基於Java語言的序列化庫。Protostuff支持的序列化格式包括:json;xml;POJO(能夠理解爲只包括屬性和對應的set/get方法的實體類)...。
典型應用見上面的代碼。
如上圖所示,將對象中的屬性和值按照tag和value的形式存儲,其中tag表示的相似於屬性編號(爲了最小化空間),value表示的是屬性的值,因此若是類中的屬性順序變化,會致使反序列化不正確。
優勢:速度快;佔用字節數很小(相比Java的序列化縮小10倍以上),適合網絡傳輸節省io;跨語言 。
缺點:反序列化時需用戶本身初始化序列化後的對象,Protostuff負責將該對象進行賦值;依賴於第三方jar類庫;二進制格式致使可讀性差;而且支持的格式比較少,比較有用的僅僅是POJO的序列化。
Fastjson是一個Java語言編寫的高性能的JSON處理器,由阿里巴巴公司開發。無依賴,不須要例外額外的jar,可以直接跑在JDK上。FastJson採用首創的算法,將parse的速度提高到極致,超過全部json庫。
嚴格意義上JSON並非序列化技術,它是將對象包裝成JSON字符串傳輸,格式相似於鍵值對形式,{"name":"Tom","sex":"M","year":20}
,因爲包含有多餘的括號,引號,冒號等,序列化後包含34字節,可是相比於Java的序列化,佔用字節數減少了三倍以上。 優勢:明文結構一目瞭然,能夠跨語言,屬性的增長減小對解析端影響較小,支持類型比Protostuff多。 缺點:字節數較多,依賴於不一樣的第三方類庫。