關於這個標題的內容是面試筆試中比較常見的考題,大家跟隨我的博客一起來學習下這個過程。
JAVA中的序列化和反序列化主要用於:
(1)將對象或者異常等寫入文件,通過文件交互傳輸信息;
(2)將對象或者異常等通過網絡進行傳輸。
那么為什么需要序列化和反序列化呢?簡單來說,如果你只是自己同一台機器的同一個環境下使用同一個JVM來操作,序列化和反序列化是沒必要的,當需要進行數據傳輸的時候就顯得十分必要。比如你的數據寫到文件里要被其他人的電腦的程序使用,或者你電腦上的數據需要通過網絡傳輸給其他人的程序使用,像服務器客戶端的這種模型就是一種應用,這個時候,大家想想,每個人的電腦配置可能不同,運行環境可能也不同,字節序可能也不同,總之很多地方都不能保證一致,所以為了統一起見,我們傳輸的數據或者經過文件保存的數據需要經過序列化和編碼等操作,相當於交互雙方有一個公共的標准,按照這種標准來做,不管各自的環境是否有差異,各自都可以根據這種標准來翻譯出自己能理解的正確的數據。
在JAVA中有專門用於此類操作的API,供開發者直接使用,對象的序列化和反序列化可以通過將對象實現Serializable接口,然后用對象的輸入輸出流進行讀寫,下面看一個完整的例子。
- package test2;
- import java.io.Serializable;
- public class DataObject implements Serializable {
- /**
- * 序列化的UID號
- */
- private static final long serialVersionUID = -3737338076212523007L;
- public static int i = 0;
- private String word = "";
- public static void setI(int i){
- DataObject.i = i;
- }
- public void setWord(String word){
- this.word = word;
- }
- public static int getI() {
- return i;
- }
- public String getWord() {
- return word;
- }
- @Override
- public String toString() {
- return "word = " + word + ", " + "i = " + i;
- }
- }
(1)類中有一個靜態成員變量i,這個變量能不能被序列化呢?等下通過測試程序看一下;
(2)類中重寫了toString方法,是為了打印結果。
接下來我們看一下測試該類的對象序列化和反序列化的一個測試程序版本,提前說明,這個版本是有問題的。
- package test2;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- /**
- * Description: 測試對象的序列化和反序列
- */
- public class TestObjSerializeAndDeserialize {
- public static void main(String[] args) throws Exception {
- // 序列化DataObject對象
- Serialize();
- // 反序列DataObject對象
- DataObject object = Deserialize();
- // 靜態成員屬於類級別的,所以不能序列化,序列化只是序列化了對象而已,
- // 這里的不能序列化的意思,是序列化信息中不包含這個靜態成員域,下面
- // 之所以i輸出還是2,是因為測試都在同一個機器(而且是同一個進程),因為這個jvm
- // 已經把i加載進來了,所以獲取的是加載好的i,如果是傳到另一台機器或者關掉程序重新
- // 寫個程序讀入DataObject.txt,此時因為別的機器或新的進程是重新加載i的,所以i信息就是初始時的信息,即0
- System.out.println(object);
- }
- /**
- * MethodName: SerializePerson
- * Description: 序列化Person對象
- * @author
- * @throws FileNotFoundException
- * @throws IOException
- */
- private static void Serialize() throws FileNotFoundException, IOException {
- DataObject object = new DataObject();
- object.setWord("123");
- object.setI(2);
- // 創建ObjectOutputStream對象輸出流,其中用到了文件的描述符對象和文件輸出流對象
- ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
- new File("DataObject.txt")));
- // 將DataObject對象存儲到DataObject.txt文件中,完成對DataObject對象的序列化操作
- oo.writeObject(object);
- System.out.println("Person對象序列化成功!");
- // 最后一定記得關閉對象描述符!!!
- oo.close();
- }
- /**
- * MethodName: DeserializePerson
- * Description: 反序列DataObject對象
- * @author
- * @return
- * @throws Exception
- * @throws IOException
- */
- private static DataObject Deserialize() throws Exception, IOException {
- // 創建ObjectInputStream對象輸入流,其中用到了文件的描述符對象和文件輸入流對象
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
- new File("DataObject.txt")));
- // 從DataObject.txt文件中讀取DataObject對象,完成對DataObject對象的反序列化操作
- DataObject object = (DataObject) ois.readObject();
- System.out.println("Person對象反序列化成功!");
- // 最后一定記得關閉對象描述符!!!
- ois.close();
- return object;
- }
- }
- word = "123", i = 2
這樣會使得大家覺得理應就是如此,其實這是錯誤的。大家要記住:
靜態成員屬於類級別的,所以不能序列化,序列化只是序列化了對象而已,這里“不能序列化”的意思是序列化信息中不包含這個靜態成員域,下面之所以i輸出還是2,是因為測試都在同一個機器(而且是同一個進程),因為這個jvm已經把i加載進來了,所以獲取的是加載好的i,如果是傳到另一台機器或者關掉程序重新寫個程序讀入DataObject.txt,此時因為別的機器或新的進程是重新加載i的,所以i信息就是初始時的信息,即0。所以,總結來看,靜態成員是不能被序列化的,靜態成員定以后的默認初始值是0,所以正確的運行結果應該是:
- word = "123", i = 0
那么既然如此,怎樣才能測試出正確的結果呢?大家注意,上面的程序是直接在一個JVM一個進程中操作完了序列化和反序列化的所有過程,故而JVM中已經保存了i = 2,所以i的值沒有變化,所以再次讀出來肯定還是2。如果想得出正確的結果,必須在兩個JVM中去測試,但是大家的電腦很難做到這種測試環境,所以可以通過以下方法來測試。
- package test2;
- import java.io.File;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.ObjectOutputStream;
- /**
- * Description: 測試對象的序列化
- */
- public class SerializeDataobject {
- public static void main(String[] args) throws Exception {
- // 序列化DataObject對象
- Serialize();
- }
- /**
- * MethodName: SerializePerson
- * Description: 序列化Person對象
- * @author
- * @throws FileNotFoundException
- * @throws IOException
- */
- private static void Serialize() throws FileNotFoundException, IOException {
- DataObject object = new DataObject();
- object.setWord("123");
- object.setI(2);
- // 創建ObjectOutputStream對象輸出流,其中用到了文件的描述符對象和文件輸出流對象
- ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
- new File("DataObject.txt")));
- // 將DataObject對象存儲到DataObject.txt文件中,完成對DataObject對象的序列化操作
- oo.writeObject(object);
- System.out.println("Person對象序列化成功!");
- // 最后一定記得關閉對象描述符!!!
- oo.close();
- }
- }
上面這個類只用來進行序列化,對象被序列化后保存在文件"DataObject.txt"中,然后程序運行結束,JVM退出。接下來看另一段程序。
- package test2;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- /**
- * Description: 測試對象的反序列
- */
- public class DeserializeDataobject {
- public static void main(String[] args) throws Exception {
- // 反序列DataObject對象
- DataObject object = Deserialize();
- // 靜態成員屬於類級別的,所以不能序列化,序列化只是序列化了對象而已,
- // 這里的不能序列化的意思,是序列化信息中不包含這個靜態成員域,下面
- // 之所以i輸出還是2,是因為測試都在同一個機器(而且是同一個進程),因為這個jvm
- // 已經把i加載進來了,所以獲取的是加載好的i,如果是傳到另一台機器或者關掉程序重新
- // 寫個程序讀入DataObject.txt,此時因為別的機器或新的進程是重新加載i的,所以i信息就是初始時的信息,即0
- System.out.println(object);
- }
- /**
- * MethodName: DeserializePerson
- * Description: 反序列DataObject對象
- * @author
- * @return
- * @throws Exception
- * @throws IOException
- */
- private static DataObject Deserialize() throws Exception, IOException {
- // 創建ObjectInputStream對象輸入流,其中用到了文件的描述符對象和文件輸入流對象
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
- new File("DataObject.txt")));
- // 從DataObject.txt文件中讀取DataObject對象,完成對DataObject對象的反序列化操作
- DataObject object = (DataObject) ois.readObject();
- System.out.println("Person對象反序列化成功!");
- // 最后一定記得關閉對象描述符!!!
- ois.close();
- return object;
- }
- }
上面這段程序用來實現對象的反序列化,它從文件"DataObject.txt"中讀出對象的相關信息,然后進行了反序列化,最終輸出對象中word和i的值,這個程序輸出的結果才是word = "123", i = 0 這個才是正確的結果,這是因為序列化和反序列化都有自己的main方法,先序列化,然后JVM退出,再次運行反序列化,JVM重新加載DataObject類,此時i = 0,"DataObject.txt"文件中其實是沒有i的信息的,只有word的信息。這里通過先后執行序列化和反序列化,讓JVM得到一次重新加載類的機會,模擬了兩個JVM下運行的結果。
總之,大家要記住以下幾點:
(1)序列化和反序列化的實現方法和應用場合;
(2)靜態成員是不能被序列化的,因為靜態成員是隨着類的加載而加載的,與類共存亡,並且靜態成員的默認初始值都是0;
(3)要明白錯誤的那個測試程序的原因,搞明白JVM的一些基本機制;
(4)要想直接通過打印對象而輸出對象的一些屬性信息,要重寫toString方法。
上面只是我的一些個人總結,歡迎大家指正和補充。