您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關如何在Java中實現序列化與反序列化,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
Java主要應用于:1. web開發;2. Android開發;3. 客戶端開發;4. 網頁開發;5. 企業級應用開發;6. Java大數據開發;7.游戲開發等。
序列化 (Serialization)是將對象的狀態信息轉換為可以存儲或傳輸的形式的過程。一般將一個對象存儲至一個儲存媒介,例如檔案或是記億體緩沖等。在網絡傳輸過程中,可以是字節或是XML等格式。而字節的或XML編碼格式可以還原完全相等的對象。這個相反的過程又稱為反序列化。
Java對象的序列化和反序列化
在Java中,我們可以通過多種方式來創建對象,并且只要對象沒有被回收我們都可以復用該對象。但是,我們創建出來的這些Java對象都是存在于JVM的堆內存中的。只有JVM處于運行狀態的時候,這些對象才可能存在。一旦JVM停止運行,這些對象的狀態也就隨之而丟失了。
但是在真實的應用場景中,我們需要將這些對象持久化下來,并且能夠在需要的時候把對象重新讀取出來。Java的對象序列化可以幫助我們實現該功能。
對象序列化機制(object serialization)是Java語言內建的一種對象持久化方式,通過對象序列化,可以把對象的狀態保存為字節數組,并且可以在有需要的時候將這個字節數組通過反序列化的方式再轉換成對象。對象序列化可以很容易的在JVM中的活動對象和字節數組(流)之間進行轉換。
在Java中,對象的序列化與反序列化被廣泛應用到RMI(遠程方法調用)及網絡傳輸中。
在Java中,只要一個類實現了java.io.Serializable
接口,那么它就可以被序列化。這里先來一段代碼:
code 1 創建一個User類,用于序列化及反序列化
public class User implements Serializable{ private String name; private int age; private Date birthday; private transient String gender; private static final long serialVersionUID = -6849794470754667710L; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + ", gender=" + gender + ", birthday=" + birthday + '}'; } }
code 2 對User進行序列化及反序列化的Demo
public class SerializableDemo { public static void main(String[] args) { //Initializes The Object User user = new User(); user.setName("hollis"); user.setGender("male"); user.setAge(23); user.setBirthday(new Date()); System.out.println(user); //Write Obj to File ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream("tempFile")); oos.writeObject(user); } catch (IOException e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(oos); } //Read Obj from File File file = new File("tempFile"); ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream(file)); User newUser = (User) ois.readObject(); System.out.println(newUser); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(ois); try { FileUtils.forceDelete(file); } catch (IOException e) { e.printStackTrace(); } } } } //output //User{name='hollis', age=23, gender=male, birthday=Tue Feb 02 17:37:38 CST 2016} //User{name='hollis', age=23, gender=null, birthday=Tue Feb 02 17:37:38 CST 2016}
1、在Java中,只要一個類實現了java.io.Serializable
接口,那么它就可以被序列化。
2、通過ObjectOutputStream
和ObjectInputStream
對對象進行序列化及反序列化
3、虛擬機是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID
)
4、序列化并不保存靜態變量。
5、要想將父類對象也序列化,就需要讓父類也實現Serializable
接口。
6、Transient 關鍵字的作用是控制變量的序列化,在變量聲明前加上該關鍵字,可以阻止該變量被序列化到文件中,在被反序列化后,transient 變量的值被設為初始值,如 int 型的是 0,對象型的是 null。
7、服務器端給客戶端發送序列化對象數據,對象中有一些數據是敏感的,比如密碼字符串等,希望對該密碼字段在序列化時,進行加密,而客戶端如果擁有解密的密鑰,只有在客戶端進行反序列化時,才可以對密碼進行讀取,這樣可以一定程度保證序列化對象的數據安全。
code 3
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; transient Object[] elementData; // non-private to simplify nested class access private int size; }
帶著這個問題,我們來看java.util.ArrayList
的源碼從上面的代碼中可以知道ArrayList實現了java.io.Serializable
接口,那么我們就可以對它進行序列化及反序列化。因為elementData是transient
的,所以我們認為這個成員變量不會被序列化而保留下來。我們寫一個Demo,驗證一下我們的想法:
code4
public static void main(String[] args) throws IOException, ClassNotFoundException { List<String> stringList = new ArrayList<String>(); stringList.add("hello"); stringList.add("world"); stringList.add("hollis"); stringList.add("chuang"); System.out.println("init StringList" + stringList); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("stringlist")); objectOutputStream.writeObject(stringList); IOUtils.close(objectOutputStream); File file = new File("stringlist"); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file)); List<String> newStringList = (List<String>)objectInputStream.readObject(); IOUtils.close(objectInputStream); if(file.exists()){ file.delete(); } System.out.println("new StringList" + newStringList); } //init StringList[hello, world, hollis, chuang] //new StringList[hello, world, hollis, chuang]
了解ArrayList的人都知道,ArrayList底層是通過數組實現的。那么數組elementData
其實就是用來保存列表中的元素的。通過該屬性的聲明方式我們知道,他是無法通過序列化持久化下來的。那么為什么code 4的結果卻通過序列化和反序列化把List中的元素保留下來了呢?
在ArrayList中定義了來個方法: writeObject
和readObject
。
這里先給出結論:
在序列化過程中,如果被序列化的類中定義了writeObject 和 readObject 方法,虛擬機會試圖調用對象類里的 writeObject 和 readObject 方法,進行用戶自定義的序列化和反序列化。
如果沒有這樣的方法,則默認調用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動態改變序列化的數值。
那么為什么ArrayList要用這種方式來實現序列化呢?
why transient
ArrayList實際上是動態數組,每次在放滿以后自動增長設定的長度值,如果數組自動增長長度設為100,而實際只放了一個元素,那就會序列化99個null元素。為了保證在序列化的時候不會將這么多null同時進行序列化,ArrayList把元素數組設置為transient。
why writeObject and readObject
前面說過,為了防止一個包含大量空對象的數組被序列化,為了優化存儲,所以,ArrayList使用transient
來聲明elementData
。 但是,作為一個集合,在序列化過程中還必須保證其中的元素可以被持久化下來,所以,通過重寫writeObject
和 readObject
方法的方式把其中的元素保留下來。
writeObject
方法把elementData
數組中的元素遍歷的保存到輸出流(ObjectOutputStream)中。
readObject
方法從輸入流(ObjectInputStream)中讀出對象并保存賦值到elementData
數組中
至此,我們先試著來回答剛剛提出的問題:
如何自定義的序列化和反序列化策略
答:可以通過在被序列化的類中增加writeObject 和 readObject方法。那么問題又來了:
雖然ArrayList中寫了writeObject 和 readObject 方法,但是這兩個方法并沒有顯示的被調用啊。
那么如果一個類中包含writeObject 和 readObject 方法,那么這兩個方法是怎么被調用的呢?
在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法時,會通過反射的方式調用。
類通過實現 java.io.Serializable
接口以啟用其序列化功能。未實現此接口的類將無法使其任何狀態序列化或反序列化。可序列化類的所有子類型本身都是可序列化的。序列化接口沒有方法或字段,僅用于標識可序列化的語義。
該接口并沒有字段和方法,為什么只有實現了該接口的對象才能被序列化呢?
在進行序列化操作時,會判斷要被序列化的類是否是Enum、Array和Serializable類型,如果不是則直接拋出NotSerializableException
。
序列化ID:
private static final long serialVersionUID = 1L;
序列化 ID在 IDE 下提供了兩種生成策略,一個設為固定的 1L,另一個是隨機生成一個不重復的 long 類型數據(實際上是使用 JDK 工具生成)。一般如果沒有特殊需求,用默認的 1L 就可以,這樣可以確保反序列化成功。因為不同的序列化id之間不能進行序列化和反序列化。
以上就是如何在Java中實現序列化與反序列化,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。