平時咱們在運行程序的時候,建立的對象都在內存中,當程序中止或者中斷了,對象也就不復存在了.若是咱們能將對象保存起來,在須要使用它的時候在拿出來使用就行了,而且對象的信息要和咱們保存
時的信息一致.序列化就能夠解決了這樣的問題.序列化固然不止一種方式,以下:html
序列類型 | 是否跨語言 | 優缺點 |
---|---|---|
hession | 支持 | 跨語言,序列化後體積小,速度較快 |
protostuff | 支持 | 跨語言,序列化後體積小,速度快,可是須要Schema,能夠動態生成 |
jackson | 支持 | 跨語言,序列化後體積小,速度較快,且具備不肯定性 |
fastjson | 支持 | 跨語言支持較困難,序列化後體積小,速度較快,只支持java,c# |
kryo | 支持 | 跨語言支持較困難,序列化後體積小,速度較快 |
fst | 不支持 | 跨語言支持較困難,序列化後體積小,速度較快,兼容jdk |
jdk | 不支持 | 序列化後體積很大,速度快 |
咱們今天介紹的就是java原生的Serializable序列化.
先列一下概念:
序列化:序列化是將對象的狀態信息轉換爲能夠存儲或傳輸的形式的過程
反序列化:從存儲或傳輸形式還原爲對象java
序列化使用起來很簡單隻須要實現Serializable接口便可,而後序列化(序列化是將對象的狀態信息轉換爲能夠存儲或傳輸的形式的過程)和反序列化(反之,從存儲或傳輸形式還原爲對象).
只要使用ObjectOutputStream和ObjectInputStream將對象轉爲二進制序列和還原爲java對象.話很少說,看下代碼示例:json
private static void testSerializable(String fileName) throws IOException { try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(fileName))) { // "XXX" 的String也能夠直接做爲對象進行反序列化的 objectOutputStream.writeObject("test serializable"); SerializableData data = new SerializableData(1, "testStr"); objectOutputStream.writeObject(data); } } private static void testDeserializable(String fileName) throws IOException, ClassNotFoundException { try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(fileName))) { String str = (String) objectInputStream.readObject(); System.out.println("String的反序列化: " + str); SerializableData readData = (SerializableData) objectInputStream.readObject(); System.out.println("反序列化的對象: " + readData.toString()); // 輸出:反序列化的對象: SerializableData(testInt=1, testStr=testStr) } } // 使用到的類 @Data @AllArgsConstructor class SerializableData implements Serializable { private Integer testInt; private String testStr; }
第一個方法是傳入文件路徑,將String和SerializableData對象序列化到fileName指定的文件中;第二個方法是反序列化將文件中的二進制還原爲java對象.
這裏其實比較簡單沒有什麼大問題,稍微提一句的就是writeObject這個方法是能夠直接將"寫入的字符串"這種形式的對象直接序列化爲二進制的.
這裏還有一點就是反序列化的版本號必須和本來對象的版本號(private static final long serialVersionUID = 1L;這個由於是本身測試因此沒有寫默認是1L,修改後,反序列化的對象版本號不一致會報錯)一致,而且jvm能找到反序列化的文件的位置,不然都會失敗.c#
簡單的使用序列化和反序列化應該沒有什麼問題,咱們再來看看transient關鍵字是啥?在某些場景下,咱們須要寫入或者還原的數據中其實有咱們不須要透露或者說不想暴露給外部的數據,若是咱們將這些隱私的數據序列化,在反序列化出來,
那麼這些信息就泄漏了.而transient關鍵字呢,就是防止這種事情的發生.
當屬性被加上了transient關鍵字之後,序列化時不會將該屬性的值給寫入,因此反序列化的時候咱們會發現本來寫入的數據,還原出來是null.
咱們寫一個例子看看是不是這樣呢?oracle
private static void testTransient() throws IOException, ClassNotFoundException { String fileName = "transientData.txt"; try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(fileName))) { Account data = new Account(1, "user1", "123456"); out.writeObject(data); } try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(fileName))) { Account readData = (Account) in.readObject(); System.out.println("transient關鍵字的對象: " + readData.toString()); // 輸出: transient關鍵字的對象: Account(id=1, userName=user1, idCardNumber=null) } } // 對應的對象 @Data @AllArgsConstructor class Account implements Serializable { private Integer id; private String userName; private transient String idCardNumber; }
這裏咱們有一個Account對象,咱們不想暴露出咱們的省份證號碼idCardNumber,因而乎加上了transient關鍵字.
而後將idCardNumber已經初始化過的data對象序列化,當咱們再反序列化去取得這個idCardNumber的值的時候,發現確實對象的idCardNumber是null,transient是起做用的.
若是是對基本類型數據加上transitent話,會獲得對應的默認值,就比如是int的數據類型,獲得的就是0.jvm
使用過自動的序列化和反序列化之後,咱們又想在序列化和反序列化的時候咱們能不能本身控制呢?在序列化和反序列化的時候咱們能不能加點日誌或者其餘的操做之類的呢?
是的,闊以的.只須要輕輕一點,實現Externalizable接口便可,和Serializable使用差很少.ide
private static void testExternalizable() throws IOException, ClassNotFoundException { String fileName = "testExternalizable.txt"; try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(fileName))) { Account2 data = new Account2("user1", 1); out.writeObject(data); } try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(fileName))) { Account2 readData = (Account2) in.readObject(); System.out.println("Externalizable的對象: " + readData.toString()); } } // 使用到的對象 @Data @AllArgsConstructor class Account2 implements Externalizable { private Integer id; private String userName; private transient String idCardNumber; @Override public void writeExternal(ObjectOutput out) throws IOException { System.out.println("執行了writeExternal方法"); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println("執行了readExternal方法"); } }
若是執行了上面的代碼,恭喜你,得到一個Exception的獎勵.大概長這樣,java.io.InvalidClassException:XXX no valid constructor,
Externalizable在執行的時候會調用默認的無參構造函數,並且記住哦,必須是public的,若是沒有加public你會發現又獎勵了一個Exception給你.
講道理這個是比較坑的.下面咱們來看看正確的用法,序列化和反序列化都是咱們本身控制的:函數
private static void testExternalizable() throws IOException, ClassNotFoundException { String fileName = "testExternalizable.txt"; try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(fileName))) { Account3 data = new Account3("user1", 1); out.writeObject(data); } try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(fileName))) { Account3 readData = (Account3) in.readObject(); System.out.println("Externalizable的對象: " + readData.toString()); /** * 輸出: * 執行了writeExternal方法 * 執行了readExternal方法 * Externalizable的對象: Account3(userName=user1, id=1) */ } } @ToString class Account3 implements Externalizable { private String userName; private Integer id; public Account3() { } public Account3(String userName, Integer id) { this.userName = userName; this.id = id; } @Override public void writeExternal(ObjectOutput out) throws IOException { System.out.println("執行了writeExternal方法"); out.writeObject(userName); out.writeInt(id); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println("執行了readExternal方法"); userName = (String) in.readObject(); id = in.readInt(); } }
1.咱們介紹了jdk自帶的序列化和反序列化(和其中的一些坑點);
2.知道了transient能夠將隱私數據不序列化;
3.還有Externalizable能夠本身來控制序列化和反序列化的進程.測試
1.https://docs.oracle.com/javas...
2.https://blog.csdn.net/do_bset...this