一.序列化簡介
1.什麼是序列化
Java平台允許我們在記憶體中建立可複用的Java對象,但一般情況下,隻有當JVM處于運作時,這些對象才可能存在,即,這些對象的生命周期不會比JVM的生命周期更長。但在現實應用中,就可能要求在JVM停止運作之後能夠儲存(持久化)指定的對象,并在将來重新讀取被儲存的對象。Java對象序列化就能夠幫助我們實作該功能。
使用Java對象序列化,在儲存對象時,會把其狀态儲存為一組位元組,在未來,再将這些位元組組裝成對象。必須注意地是,對象序列化儲存的是對象的"狀态",即它的成員變量。由此可知,對象序列化不會關注類中的靜态變量。
除了在持久化對象時會用到對象序列化之外,當使用RMI(遠端方法調用),或在網絡中傳遞對象時,都會用到對象序列化。Java序列化API為處理對象序列化提供了一個标準機制,該API簡單易用,在本文的後續章節中将會陸續講到。
2.序列化和持久化
序列化 : 把對象轉存為二進制資料(如網絡傳輸,存儲資料庫等),必須實作序列化接口 (Java.io.Serializable).
持久化 : 把對象儲存在媒體上(如寫檔案,讀檔案不是), 沒有接口實作,一般指方法調用.
解釋說明:
如果按照存儲媒體和生命周期的長短劃分,所有的資料都以兩種形式存在,其中:
一種是儲存于記憶體中的運作時對象;另一種則是存儲于持久化實體媒體中的檔案,比如資料庫檔案等。
資料的持久化關注于相同的資料在不同形态資料之間的轉化,解決的是如何将記憶體對象持久化存儲,以及從實體媒體中加載資料并建立記憶體對象。
資料的持久化是序列化的又一個典型的應用,對象隻有在序列化之後才能進行持久化存儲,從持久化存儲媒體加載的資料通過反序列化轉變成運作時對象。
二.什麼情況需要序列化
a)當你想把的記憶體中的對象儲存到一個檔案中或者資料庫中時候;
b)當你想用套接字在網絡上傳送對象的時候;
c)當你想通過RMI傳輸對象的時候;
三.序列化和反序列化執行個體
java序列化主要使用ObjectOutputStream.write()方法,反序列化使用ObjectInputStream.read()方法。
package com.java.serializable.test2;
import java.io.*;
/**
* Created by laobo on 2016/1/6.
*/
public class TestSerializable {
public static void main(String[] arg) throws IOException, ClassNotFoundException {
SerializablePerson();
DeserializablePerson();
}
private static void SerializablePerson() throws IOException {
Person person = new Person();
person.setName("laobo");
person.setAge(27);
person.setSex("男");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("/Users/duanxiangchao/java_test/person.txt")));
objectOutputStream.writeObject(person);
System.out.println("Person序列化成功!");
objectOutputStream.close();
}
private static void DeserializablePerson() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("/Users/duanxiangchao/java_test/person.txt")));
Person person = (Person)objectInputStream.readObject();
System.out.println("Person反序列化成功!");
System.out.println("姓名:" + person.getName());
System.out.println("性别:" + person.getSex());
System.out.println("年齡:" + person.getAge());
}
}
package com.java.serializable.test2;
import java.io.Serializable;
/**
* Created by duanxiangchao on 2016/1/6.
*/
public class Person implements Serializable {
private static final long serialVersionUID = -5809782578272943999L;
private String name;
private String sex;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
執行程式,會在java_test檔案夾下生成person.txt
console輸出如下
Person序列化成功!
Person反序列化成功!
姓名:laobo
性别:男
年齡:27
四.序列化常見問題
1. Serializable的作用
為什麼一個類實作了Serializable接口,它就可以被序列化呢?在上節的示例中,使用ObjectOutputStream來持久化對象,在該類中有如下代碼:
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
......省略
// 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);
}
}
從上述代碼可知, 如果被寫對象的類型是String,或數組,或Enum,或Serializable,那麼就可以對該對象進行序列化,否則将抛出NotSerializableException。
2.預設序列化機制和transient關鍵字
預設機制:在序列化對象時,不僅會序列化目前對象本身,還會對該對象引用的其它對象也進行序列化,同樣地,這些其它對象引用的另外對象也将被序列化,以此類推。
是以,如果一個對象包含的成員變量是容器類對象,而這些容器所含有的元素也是容器類對象,那麼這個序列化的過程就會較複雜,開銷也較大。
transient關鍵字
被transient修飾的屬性,不會序列化到指定的目标中。
例如:上面Person中的屬性sex設定為 transient
transient private String sex;
則輸出結果為:
Person序列化成功!
Person反序列化成功!
姓名:laobo
性别:null
年齡:27
3.Java對象序列化成字元串和反序列化
上邊的例子,是把對象序列化到檔案中;更多的時候,我們需要把序列化的對象儲存的資料庫或者緩存,這就需要把序列化的對象義字元串的形式傳回。這就需要用到ByteArrayOutputStream和ByteArrayInputStream作為ObjectOutputStream、ObjectInputStream的緩沖區,具體使用如下:
/**
* Created by laobo on 16/11/10.
*/
public class SerializableToString {
private static final String TEMP_ENCODING = "ISO-8859-1";
private static final String DEFAULT_ENCODING = "UTF-8";
public static void main(String[] args) {
Person person = new Person("zhangsan", "男", 25);
String str = writeToStr(person);
System.out.println(str);
Person person1 = (Person) writeToObject(str);
System.out.println("姓名:" + person1.getName());
System.out.println("性别:" + person1.getSex());
System.out.println("年齡:" + person1.getAge());
}
private static String writeToStr(Object obj) {
//使用ByteArrayOutputStream緩沖區接收序列化對象
//緩沖區會随着資料的不斷寫入而自動增長;可使用toByteArray()和toString()擷取資料
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//專門用于序列化對象的流
ObjectOutputStream oos = null;
String serializableStr = null;
try {
oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
//使用ByteArrayOutputStream.toString()方法,傳回字元串
serializableStr = bos.toString(TEMP_ENCODING);
serializableStr = URLEncoder.encode(serializableStr, DEFAULT_ENCODING);
return serializableStr;
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
private static Object writeToObject(String str) {
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
String str1 = URLDecoder.decode(str, DEFAULT_ENCODING);
byte[] bytes = str1.getBytes(TEMP_ENCODING);
bais = new ByteArrayInputStream(bytes);
ois = new ObjectInputStream(bais);
Person person = (Person) ois.readObject();
return person;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
4.Java序列化版本serialVersionUID的作用
通常在需要序列化的類中回生命如下屬性
private static final long serialVersionUID = -5809782578272943999L;
簡單來說,Java的序列化機制是通過在運作時判斷類的serialVersionUID來驗證版本一緻性的。在進行反序列化時,JVM會把傳來的位元組流中的serialVersionUID與本地相應實體(類)的serialVersionUID進行比較,如果相同就認為是一緻的,可以進行反序列化,否則就會出現序列化版本不一緻的異常。
當實作java.io.Serializable接口的實體(類)沒有顯式地定義一個名為serialVersionUID,類型為long的變量時,Java序列化機制會根據編譯的class自動生成一個serialVersionUID作序列化版本比較用,這種情況下,隻有同一次編譯生成的class才會生成相同的serialVersionUID 。
如果我們不希望通過編譯來強制劃分軟體版本,即實作序列化接口的實體能夠相容先前版本,未作更改的類,就需要顯式地定義一個名為serialVersionUID,類型為long的變量,不修改這個變量值的序列化實體都可以互相進行串行化和反串行化。
serialVersionUID主要來判斷不同版本的相容性。