Java序列化與反序列化

用途

  1) 把對象的字節序列永久地保存到硬盤上,一般存放在一個文件中。
  2) 在網絡上傳送對象的字節序列。
 

操做類

java.io.ObjectOutputStream
 
  表明對象輸出流,它的writeObject(Object obj)方法可對參數指定的obj對象進行序列化,把獲得的字節序列寫到一個目標輸出流中。
 
java.io.ObjectInputStream
 
  表明對象輸入流,它的readObject()方法從一個源輸入流中讀取字節序列,再把它們反序列化爲一個對象,並將其返回。
 

步驟

對象序列化包括以下步驟:
  1) 建立一個對象輸出流,它能夠包裝一個其餘類型的目標輸出流,如文件輸出流;
  2) 經過對象輸出流的writeObject()方法寫對象。
 
對象反序列化的步驟以下:
  1) 建立一個對象輸入流,它能夠包裝一個其餘類型的源輸入流,如文件輸入流;
  2) 經過對象輸入流的readObject()方法讀取對象。
 

前提

  只有實現了Serializable和Externalizable接口的類的對象才能被序列化。Externalizable接口繼承自 Serializable接口,實現Externalizable接口的類徹底由自身來控制序列化的行爲,而僅實現Serializable接口的類能夠 採用默認的序列化方式 。
 

serialVersionUID

  字面意思上是序列化的版本號,凡是實現Serializable接口的類都有一個表示序列化版本標識符的靜態變量。
 
  主要是輔助序列化和反序列化,序列化和反序列化的時候,serialVersionUID必須一致,反序列化纔會成功。固然,若是不顯式的聲明 serialVersionUID,系統在進行序列化的時候會根據當前類的特徵進行哈希運算,最終獲得一個版本號。
 
  序列化操做的時候系統會把當前類的serialVersionUID寫入到序列化文件中,當反序列化時系統會去檢測文件中的serialVersionUID,判斷它是否與當前類的serialVersionUID一致,若是一致就說明序列化類的版本與當前類版本是同樣的,能夠反序列化成功,不然失敗。
 
  爲了提升serialVersionUID的獨立性和肯定性,強烈建議在一個可序列化類中顯示的定義serialVersionUID,爲它賦予明確的值。
 

自動生成

  IDEA下設置自動生成 serialVersionUID
 
  輸入:serialVer 
 
這時候再來看看user對象,已經有黃色背景的警告了。
 
雙擊User對象,按 Alt + Enter 鍵,會彈出一個下面那樣的小框,而後直接Enter確認便可。
 
接下來就會自動生成一個版本id
 
固然了,java也提供了方法讓咱們來本身生成這個版本號。
public static void main(String[] args) { ObjectStreamClass osc = ObjectStreamClass.lookup(User.class); long id = osc.getSerialVersionUID(); System.out.println("用戶對象版本號:" + id); }

 

運行結果與咱們自動生成的值同樣(由於對象並無發生改變)
用戶對象版本號:-6389397398684165955

 

生成原理

首先提早對象特徵:類名,屬性名,屬性類型,方法等等。而後使用SHA摘要算法進行哈希運算。
感興趣的能夠去這個getSerialVersionUID()方法裏面看看。
截圖爲證:
 

部分代碼

序列化

ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { ObjectOutputStream oos = new ObjectOutputStream(bos); //準備寫入用戶對象
 oos.writeObject(user); oos.flush(); //序列化後獲得的字符串
    String value = Hex.toHexString(bos.toByteArray()); bos.close(); } catch (IOException e) { e.printStackTrace(); }

 

反序列化

byte[] serialized = Hex.decode(value); ByteArrayInputStream bis = new ByteArrayInputStream(serialized); ObjectInputStream ois = new ObjectInputStream(bis); User user = (User) ois.readObject();

 

實例

不實現序列化接口
import org.bouncycastle.util.encoders.Hex; import java.io.*; /** * 序列化與反序列測試 * * @author wzm * @version 1.0.0 * @date 2020/1/27 18:21 **/
public class JavaSerializationTest { static class User { String name; String sex; int age; User(String name, String sex, int age) { this.name = name; this.sex = sex; this.age = age; } @Override public String toString() { return "User{" +
                    "name='" + name + '\'' +
                    ", sex='" + sex + '\'' +
                    ", age=" + age +
                    '}'; } } private static String save(User user) { String value = ""; try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos)) { //準備寫入用戶對象
 oos.writeObject(user); oos.flush(); //序列化後獲得的字符串
            value = Hex.toHexString(bos.toByteArray()); System.out.println("用戶:" + value); } catch (IOException e) { e.printStackTrace(); System.out.println(e.getMessage()); } return value; } private static User get(String userStr) { try { byte[] serialized = Hex.decode(userStr); ByteArrayInputStream bis = new ByteArrayInputStream(serialized); ObjectInputStream ois = new ObjectInputStream(bis); return (User) ois.readObject(); } catch (Exception e) { e.printStackTrace(); System.out.println(e.getMessage()); throw new RuntimeException(e); } } public static void main(String[] args){ User user = new User("小明", "男", 20); String userStr = save(user); User user1 = get(userStr); System.out.println(user1); } } 

 

報錯以下:
排查錯誤的緣由每每須要從根源着手。
java.io.NotSerializableException: fabric.edu.common.JavaSerializationTest$User fabric.edu.common.JavaSerializationTest$User at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at fabric.edu.common.JavaSerializationTest.save(JavaSerializationTest.java:42) at fabric.edu.common.JavaSerializationTest.main(JavaSerializationTest.java:69) java.io.EOFException null at java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2681) at java.io.ObjectInputStream$BlockDataInputStream.readShort(ObjectInputStream.java:3156) at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:862) at java.io.ObjectInputStream.<init>(ObjectInputStream.java:358) at fabric.edu.common.JavaSerializationTest.get(JavaSerializationTest.java:58) at fabric.edu.common.JavaSerializationTest.main(JavaSerializationTest.java:70) Exception in thread "main" java.lang.RuntimeException: java.io.EOFException at fabric.edu.common.JavaSerializationTest.get(JavaSerializationTest.java:63) at fabric.edu.common.JavaSerializationTest.main(JavaSerializationTest.java:70) Caused by: java.io.EOFException at java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2681) at java.io.ObjectInputStream$BlockDataInputStream.readShort(ObjectInputStream.java:3156) at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:862) at java.io.ObjectInputStream.<init>(ObjectInputStream.java:358) at fabric.edu.common.JavaSerializationTest.get(JavaSerializationTest.java:58) ... 1 more

 

而後去源碼裏面看看(ObjectOutputStream.java:1184)
private void writeObject0(Object obj, boolean unshared) throws IOException { boolean oldMode = bout.setBlockDataMode(false); depth++; try { // handle previously written and non-replaceable objects
        int h; if ((obj = subs.lookup(obj)) == null) { writeNull(); return; } else if (!unshared && (h = handles.lookup(obj)) != -1) { writeHandle(h); return; } else if (obj instanceof Class) { writeClass((Class) obj, unshared); return; } else if (obj instanceof ObjectStreamClass) { writeClassDesc((ObjectStreamClass) obj, unshared); return; } // check for replacement object
        Object orig = obj; Class<?> cl = obj.getClass(); ObjectStreamClass desc; for (;;) { // REMIND: skip this check for strings/arrays?
            Class<?> repCl; desc = ObjectStreamClass.lookup(cl, true); if (!desc.hasWriteReplaceMethod() || (obj = desc.invokeWriteReplace(obj)) == null || (repCl = obj.getClass()) == cl) { break; } cl = repCl; } if (enableReplace) { Object rep = replaceObject(obj); if (rep != obj && rep != null) { cl = rep.getClass(); desc = ObjectStreamClass.lookup(cl, true); } obj = rep; } // if object replaced, run through original checks a second time
        if (obj != orig) { subs.assign(orig, obj); if (obj == null) { writeNull(); return; } else if (!unshared && (h = handles.lookup(obj)) != -1) { writeHandle(h); return; } else if (obj instanceof Class) { writeClass((Class) obj, unshared); return; } else if (obj instanceof ObjectStreamClass) { writeClassDesc((ObjectStreamClass) obj, unshared); return; } } // remaining cases
        if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum<?>) obj, desc, unshared); } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } } } finally { depth--; bout.setBlockDataMode(oldMode); } }

 

實現序列化接口
從拋出的異常就能夠肯定是沒有實現序列化: 所以對這個用戶對象稍加修改:

 

static class User implements Serializable { String name; String sex; int age; User(String name, String sex, int age) { this.name = name; this.sex = sex; this.age = age; } @Override public String toString() { return "User{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age=" + age +
                '}'; } }

 

執行結果:
用戶:aced00057372002c6661627269632e6564752e636f6d6d6f6e2e4a61766153657269616c697a6174696f6e546573742455736572e16c1276e8beef3b0200034900036167654c00046e616d657400124c6a6176612f6c616e672f537472696e673b4c000373657871007e0001787000000014740006e5b08fe6988e740003e794b7 User{name='小明', sex='男', age=20}
這樣的話用戶就能夠正常序列化了,能夠用任何可能的方式存儲起來(持久化),而後再須要使用的時候再反序列化(恢復)出來。
相關文章
相關標籤/搜索