參考資料:http://www.cnblogs.com/yangjian-java/p/7813623.htmlhtml
一、序列化是幹什麼的?java
Java平臺容許咱們在內存中建立可複用的Java對象,但通常狀況下,只有當JVM處於運行時,這些對象纔可能存在,即,這些對象的生命週期不會比JVM的生命週期更長。數據庫
但在現實應用中,就可能要求在JVM中止運行以後可以保存(持久化)指定的對象,並在未來從新讀取被保存的對象。Java對象序列化就可以幫助咱們實現該功能。網絡
使用Java對象序列化,在保存對象時,會把其狀態保存爲一組字節,在將來,再將這些字節組裝成對象。必須注意地是,對象序列化保存的是對象的"狀態",即它的成員變量。由此可知,對象序列化不會關注類中的靜態變量!!!jvm
除了在持久化對象時會用到對象序列化以外,當使用RMI(遠程方法調用),或在網絡中傳遞對象時,都會用到對象序列化。Java序列化API爲處理對象序列化提供了一個標準機制,該API簡單易用,之後筆者會逐步介紹之。ide
二、什麼狀況下須要序列化
a)當你想把的內存中的對象狀態保存到一個文件中或者數據庫中時候;
b)當你想用套接字在網絡上傳送對象的時候;
c)當你想經過RMI傳輸對象的時候;測試
簡而言之:序列化的做用就是爲了避免同jvm之間共享實例對象的一種解決方案.由java提供此機制,效率之高,是其餘解決方案沒法比擬的.自家的東西嘛.this
三、簡單示例spa
首先建立一個Person類debug
import java.io.Serializable; public class Person implements Serializable{ private String name; private Integer age; private Gender gender; public Person() { System.out.println("Persong no arg constructor"); } public Person(String name, Integer age, Gender gender) { System.out.println("Persong all args constructor"); this.name = name; this.age = age; this.gender = gender; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Gender getGender() { return gender; } public void setGender(Gender gender) { this.gender = gender; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + ", gender=" + gender + "]"; } }
PS:該類必須實現Serializable接口,不然會報java.io.NotSerializableException: serializable.Person的錯誤(讀者不仿一會試試去掉Serializable試試)
其中,Gender是個枚舉類
/** * 性別枚舉類 */ public enum Gender { MALE,FEMALE; } /** * 每一個枚舉類型都會默認繼承類java.lang.Enum,而該類實現了Serializable接口,因此枚舉類型對象都是默承認以被序列化的 */
下面,就是測試代碼:
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SimpleSerializableTest { public static void main(String[] args)throws Exception { //首先,指定輸出文件 File file = new File("person.out"); //建立一個FileOutputStream FileOutputStream fo = new FileOutputStream(file); //建立一個ObjectOutputStream ObjectOutputStream oo = new ObjectOutputStream(fo); //建立一個Person對象 Person person = new Person("李同窗",18,Gender.MALE); oo.writeObject(person); oo.close(); //再將person讀出來 //建立一個ObjectInputStream ObjectInputStream oi = new ObjectInputStream(new FileInputStream(file)); Object readObject = oi.readObject(); oi.close(); System.out.println(readObject); } }
運行這段代碼,你將看到以下輸出:
Persong all args constructor //這個是在建立Person的時候調用的 Person [name=李同窗, age=18, gender=MALE] //這個實在最後打印readObject的時候調用的
咱們能夠看到,咱們成功將建立的對象寫入到person.out文件中,而後經過readObject將其讀出來(是讀出一個已序列化的對象,即直接使用字節將Person對象還原出來的,而不是new一個,能夠看到也沒有調用構造器嘛)
然,當Person對象被保存到person.out文件中以後,咱們能夠在其它地方去讀取該文件以還原對象,以下面代碼:
import java.io.File; import java.io.FileInputStream; import java.io.ObjectInputStream; public class OtherReadPersonSerializable { public static void main(String[] args)throws Exception { File file = new File("person.out"); ObjectInputStream oi = new ObjectInputStream(new FileInputStream(file)); Object otherRead = oi.readObject(); oi.close(); System.out.println(otherRead); } }
仍然能夠輸入以下內容:
Person [name=李同窗, age=18, gender=MALE]
但必須確保該讀取程序的CLASSPATH中包含有Person.class(哪怕在讀取Person對象時並無顯示地使用Person類,如上例所示),不然會拋出ClassNotFoundException。
四、Serializable的做用
爲何一個類實現了Serializable接口,它就能夠被序列化呢?在上節的示例中,使用ObjectOutputStream來持久化對象,在該類中有以下代碼:
1 if (obj instanceof String) { 2 writeString((String) obj, unshared); 3 } else if (cl.isArray()) { 4 writeArray(obj, desc, unshared); 5 } else if (obj instanceof Enum) { 6 writeEnum((Enum<?>) obj, desc, unshared); 7 } else if (obj instanceof Serializable) { 8 writeOrdinaryObject(obj, desc, unshared); 9 } else { 10 if (extendedDebugInfo) { 11 throw new NotSerializableException( 12 cl.getName() + "\n" + debugInfoStack.toString()); 13 } else { 14 throw new NotSerializableException(cl.getName()); 15 } 16 }
看代碼第7行,作了Serializable類型判斷,從這段代碼中也能夠知道,能夠被序列化的是String,Array,Enum以及實現了Serializable接口的類
五、默認序列化機制
若是僅僅只是讓某個類實現Serializable接口,而沒有其它任何處理的話,則就是使用默認序列化機制。使用默認機制,在序列化對象時,不只會序列化當前對象自己,還會對該對象引用的其它對象也進行序列化,一樣地,這些其它對象引用的另外對象也將被序列化,以此類推。因此,若是一個對象包含的成員變量是容器類對象,而這些容器所含有的元素也是容器類對象,那麼這個序列化的過程就會較複雜,開銷也較大。
六、 影響序列化
在現實應用中,有些時候不能使用默認序列化機制。好比,但願在序列化過程當中忽略掉敏感數據,或者簡化序列化過程。下面將介紹若干影響序列化的方法。
6.1 transient關鍵字
當某個字段被聲明爲transient後,默認序列化機制就會忽略該字段。好比,將Person中age生命爲transient:
transient private Integer age;
那麼,當你再運行SimpleSerializableTest 的時候,就會有以下輸出:
Persong all args constructor
Person [name=李同窗, age=null, gender=MALE]
你看,這個時候age就爲null了,未被序列化到person.out中。
6.2 writeObject()方法與readObject()方法
對於上述已被聲明爲transitive的字段age,除了將transitive關鍵字去掉以外,是否還有其它方法能使它再次可被序列化?方法之一就是在Person類中添加兩個方法:writeObject()與readObject(),以下所示:
public class Person implements Serializable { private String name; transient private Integer age; //未去掉transient private Gender gender; private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeInt(age); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); age = in.readInt(); } ... // 其餘不變 }
在writeObject()方法中會先調用ObjectOutputStream中的defaultWriteObject()方法,該方法會執行默認的序列化機制,如6.1節所述,此時會忽略掉age字段。而後再調用writeInt()方法顯示地將age字段寫入到ObjectOutputStream中。readObject()的做用則是針對對象的讀取,其原理與writeObject()方法相同。
Persong all args constructor
Person [name=李同窗, age=18, gender=MALE]
必須注意地是,writeObject()與readObject()都是private方法,那麼它們是如何被調用的呢?毫無疑問,是使用反射。詳情可見ObjectOutputStream中的writeSerialData方法,以及ObjectInputStream中的readSerialData方法。