您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關如何在Java中實現(xiàn)序列化與反序列化,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
Java主要應用于:1. web開發(fā);2. Android開發(fā);3. 客戶端開發(fā);4. 網(wǎng)頁開發(fā);5. 企業(yè)級應用開發(fā);6. Java大數(shù)據(jù)開發(fā);7.游戲開發(fā)等。
序列化 (Serialization)是將對象的狀態(tài)信息轉換為可以存儲或傳輸?shù)男问降倪^程。一般將一個對象存儲至一個儲存媒介,例如檔案或是記億體緩沖等。在網(wǎng)絡傳輸過程中,可以是字節(jié)或是XML等格式。而字節(jié)的或XML編碼格式可以還原完全相等的對象。這個相反的過程又稱為反序列化。
Java對象的序列化和反序列化
在Java中,我們可以通過多種方式來創(chuàng)建對象,并且只要對象沒有被回收我們都可以復用該對象。但是,我們創(chuàng)建出來的這些Java對象都是存在于JVM的堆內(nèi)存中的。只有JVM處于運行狀態(tài)的時候,這些對象才可能存在。一旦JVM停止運行,這些對象的狀態(tài)也就隨之而丟失了。
但是在真實的應用場景中,我們需要將這些對象持久化下來,并且能夠在需要的時候把對象重新讀取出來。Java的對象序列化可以幫助我們實現(xiàn)該功能。
對象序列化機制(object serialization)是Java語言內(nèi)建的一種對象持久化方式,通過對象序列化,可以把對象的狀態(tài)保存為字節(jié)數(shù)組,并且可以在有需要的時候將這個字節(jié)數(shù)組通過反序列化的方式再轉換成對象。對象序列化可以很容易的在JVM中的活動對象和字節(jié)數(shù)組(流)之間進行轉換。
在Java中,對象的序列化與反序列化被廣泛應用到RMI(遠程方法調用)及網(wǎng)絡傳輸中。
在Java中,只要一個類實現(xiàn)了java.io.Serializable
接口,那么它就可以被序列化。這里先來一段代碼:
code 1 創(chuàng)建一個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中,只要一個類實現(xiàn)了java.io.Serializable
接口,那么它就可以被序列化。
2、通過ObjectOutputStream
和ObjectInputStream
對對象進行序列化及反序列化
3、虛擬機是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID
)
4、序列化并不保存靜態(tài)變量。
5、要想將父類對象也序列化,就需要讓父類也實現(xiàn)Serializable
接口。
6、Transient 關鍵字的作用是控制變量的序列化,在變量聲明前加上該關鍵字,可以阻止該變量被序列化到文件中,在被反序列化后,transient 變量的值被設為初始值,如 int 型的是 0,對象型的是 null。
7、服務器端給客戶端發(fā)送序列化對象數(shù)據(jù),對象中有一些數(shù)據(jù)是敏感的,比如密碼字符串等,希望對該密碼字段在序列化時,進行加密,而客戶端如果擁有解密的密鑰,只有在客戶端進行反序列化時,才可以對密碼進行讀取,這樣可以一定程度保證序列化對象的數(shù)據(jù)安全。
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實現(xiàn)了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底層是通過數(shù)組實現(xiàn)的。那么數(shù)組elementData
其實就是用來保存列表中的元素的。通過該屬性的聲明方式我們知道,他是無法通過序列化持久化下來的。那么為什么code 4的結果卻通過序列化和反序列化把List中的元素保留下來了呢?
在ArrayList中定義了來個方法: writeObject
和readObject
。
這里先給出結論:
在序列化過程中,如果被序列化的類中定義了writeObject 和 readObject 方法,虛擬機會試圖調用對象類里的 writeObject 和 readObject 方法,進行用戶自定義的序列化和反序列化。
如果沒有這樣的方法,則默認調用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動態(tài)改變序列化的數(shù)值。
那么為什么ArrayList要用這種方式來實現(xiàn)序列化呢?
why transient
ArrayList實際上是動態(tài)數(shù)組,每次在放滿以后自動增長設定的長度值,如果數(shù)組自動增長長度設為100,而實際只放了一個元素,那就會序列化99個null元素。為了保證在序列化的時候不會將這么多null同時進行序列化,ArrayList把元素數(shù)組設置為transient。
why writeObject and readObject
前面說過,為了防止一個包含大量空對象的數(shù)組被序列化,為了優(yōu)化存儲,所以,ArrayList使用transient
來聲明elementData
。 但是,作為一個集合,在序列化過程中還必須保證其中的元素可以被持久化下來,所以,通過重寫writeObject
和 readObject
方法的方式把其中的元素保留下來。
writeObject
方法把elementData
數(shù)組中的元素遍歷的保存到輸出流(ObjectOutputStream)中。
readObject
方法從輸入流(ObjectInputStream)中讀出對象并保存賦值到elementData
數(shù)組中
至此,我們先試著來回答剛剛提出的問題:
如何自定義的序列化和反序列化策略
答:可以通過在被序列化的類中增加writeObject 和 readObject方法。那么問題又來了:
雖然ArrayList中寫了writeObject 和 readObject 方法,但是這兩個方法并沒有顯示的被調用啊。
那么如果一個類中包含writeObject 和 readObject 方法,那么這兩個方法是怎么被調用的呢?
在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法時,會通過反射的方式調用。
類通過實現(xiàn) java.io.Serializable
接口以啟用其序列化功能。未實現(xiàn)此接口的類將無法使其任何狀態(tài)序列化或反序列化??尚蛄谢惖乃凶宇愋捅旧矶际强尚蛄谢?。序列化接口沒有方法或字段,僅用于標識可序列化的語義。
該接口并沒有字段和方法,為什么只有實現(xiàn)了該接口的對象才能被序列化呢?
在進行序列化操作時,會判斷要被序列化的類是否是Enum、Array和Serializable類型,如果不是則直接拋出NotSerializableException
。
序列化ID:
private static final long serialVersionUID = 1L;
序列化 ID在 IDE 下提供了兩種生成策略,一個設為固定的 1L,另一個是隨機生成一個不重復的 long 類型數(shù)據(jù)(實際上是使用 JDK 工具生成)。一般如果沒有特殊需求,用默認的 1L 就可以,這樣可以確保反序列化成功。因為不同的序列化id之間不能進行序列化和反序列化。
以上就是如何在Java中實現(xiàn)序列化與反序列化,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降摹OM隳芡ㄟ^這篇文章學到更多知識。更多詳情敬請關注億速云行業(yè)資訊頻道。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。