這幾天在看到設計模式時,看到有關于序列化的知識,發現自己之前這塊知識很欠缺,是以這花了兩天仔細研究了一下,感覺這個東西還是很有趣的(當然也很有用-。+),今天給大家帶來這篇文章,希望對大家有幫助。
序列化概念和應用
首先我們需要知道序列化是個什麼意思。
- 序列化:将對象轉換為可傳輸的二進制流的過程。
- 反序列化:把位元組序列恢複成對象的過程。
我舉個栗子:我們都進行過檔案操作吧。我們在檔案中寫數字,或者寫漢字,再或者寫英文,這些東西都是可以直接轉為二進制位元組的,我們把需要的資料存儲在File中,實作了資料存儲到裝置中,實作了資料持久化。
但是現在我有一個對象需要暫時存儲到檔案中,這個跟剛才的基本類型不一樣了哦。是以說怎麼辦?
還有這個情況:我們在進行網絡傳輸,傳輸賬号、密碼等,這些都是基本資料。那我如果想傳輸一個對象過去呢?到了對方電腦上之後,鬼知道你這是個什麼東西。。。。
根據這兩種案例,相信大家已經對序列化的應用有所了解了,主要可以分為以下幾類:
- 存儲到檔案或本地資料庫(如SQLite)這類持久化資料操作。
- 通過網絡請求傳輸對象。
- 記憶體中資料調用(Intent,下面會多次用到)。
一句話概括,就是對象進行存儲和傳遞的一種方法:轉化為二進制流。
而進行序列化主要有兩個重要的類:Serializable類和Parcelable類。我們分别來看一下。
Serializable類
首先說一下這個類,相信在Java中大家可能已經見到過這個類了。這裡簡單介紹一下:
public interface Serializable {
}
Serializable是一個标記接口,從上面的類中也可以看出來,他是一個空的接口。
我們傳輸時候,各種各樣的類,我們需要有一個類來對他們進行統一。但是Java中是單根繼承,而接口恰好彌補了這個地方。我們可以将實作的借口看做父類,這樣通過Serializable類就實作了多種類的統一,同時為類打上了标簽,可以通過instanceof Serializable判斷是否能序列化(個人了解)。
而它使用起來也很簡單,隻将需要序列化的類實作Serializable類就好了,其他的我們完全不用操作。
Serializable在Java中就有了,我們先來看一下在Java中它是如何使用:
Java中使用Serializable
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2LcBnRtlFcwJDW1A3MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2LcRHelR3LcJzLctmch1mclRXY39jM0MDNxkjM5ATNxYDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
盜取了一張大神的圖,我們在學習IO流中,有兩個類是用于讀寫對象的——ObjectInputStream和ObjectOutputStream。
ObjectOutputStream負責将記憶體中的對象寫入存儲中。ObjectInputStream負責從本地存儲中讀取存入的對象。而是用這兩個類,那麼傳入的這個類必須是序列化的。我們看一下代碼就知道了:
import java.io.Serializable;
public class StudentBean implements Serializable{
private int age;
private String name;
private int id;
public StudentBean(int age, String name, int id) {
super();
this.age = age;
this.name = name;
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "StudentBean [age=" + age + ", name=" + name + ", id=" + id + "]";
}
}
這是一個Bean類,實作了get和set方法,構造方法和重寫了toString方法。
public static ObjectOutputStream serilizeData(StudentBean studentBean) {
ObjectOutputStream objectOutputStream = null ;
try {
objectOutputStream = new ObjectOutputStream(
new FileOutputStream(
new File("/Users/jibai/Desktop/" + StudentBean.class.getSimpleName() + ".txt")
)
);
objectOutputStream.writeObject(studentBean);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return objectOutputStream;
}
這是我們将序列化的Bean類通過類輸入流寫入檔案的方法。
public static StudentBean reverseSerilizeData(String path) {
StudentBean bean = null;
try {
ObjectInputStream objectInputStream = new ObjectInputStream(
new FileInputStream(
new File(path)
)
);
bean = (StudentBean) objectInputStream.readObject();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return bean;
}
這個是将本地存儲對象的檔案讀取出來的方法。
最後看一下main方法:
public static void main(String[] args) {
// TODO Auto-generated method stub
serilizeData(new StudentBean(18, "tom", 01));
StudentBean bean = reverseSerilizeData("/Users/jibai/Desktop/" + StudentBean.class.getSimpleName() + ".txt");
System.out.println(bean.toString());
}
首先把一個StudentBean的對象存儲到檔案中,然後再将這個檔案中的對象讀取出來,我們看一下結果:
桌面确實有StudentBean這個檔案,而且控制台也成功得把這個檔案中的對象讀出來了。
如果我們沒有讓StudentBean類實作Serializable接口,會怎麼樣。
java.io.NotSerializableException: StudentBean
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at MyProject.serilizeData(MyProject.java:30)
at MyProject.main(MyProject.java:14)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: StudentBean
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1575)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at MyProject.reverseSerilizeData(MyProject.java:51)
at MyProject.main(MyProject.java:15)
Caused by: java.io.NotSerializableException: StudentBean
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at MyProject.serilizeData(MyProject.java:30)
at MyProject.main(MyProject.java:14)
Exception in thread "main" java.lang.NullPointerException
at MyProject.main(MyProject.java:16)
第一行很明顯,抛出了一個異常,說StudentBean類沒有序列化。
Android下Serializable
關于這個實在沒什麼特别需要講的,因為使用也隻是實作這個接口就好了。等會在Parcelable中會順便提一下。
小細節
其實關于Serializable還有一個知識點需要了解:我們在實作Serializable時,類會有一個序列化的值,這個值預設的情況下會随我們Bean類中屬性成員變化而變化。
如果在eclipse中沒有定義這個值,那麼類名會有一個警告:
public class StudentBean implements Serializable{
/**
*
*/
private int age;
private String name;
private int id;
添加了UID之後:
private static final long serialVersionUID = 630907180238371889L;
/**
*
*/
private int age;
private String name;
private int id;
如果對類成員屬性進行了修改,那麼再次讀取這個對象時,會因為UID不同而抛出異常:
java.io.InvalidClassException: StudentBean; local class incompatible: stream classdesc serialVersionUID = -8088515121892865631, local class serialVersionUID = 630907180238371889
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1883)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1749)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2040)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at MyProject.reverseSerilizeData(MyProject.java:51)
at MyProject.main(MyProject.java:15)
Exception in thread "main" java.lang.NullPointerException
at MyProject.main(MyProject.java:16)
是以我們在實作Serializable接口同時,在類中要聲明UID。
Parcelable類
Parcelable類相比于Serializable,要複雜多了,Parcelable類是Android推出的高效的序列化接口(據說和Serializable性能對比,是其10倍速度,真的假的我不知道)。
先看一下這個接口的結構:
public class RectBean implements Parcelable {
public RectBean() {
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
}
protected RectBean(Parcel in) {
}
public static final Creator<RectBean> CREATOR = new Creator<RectBean>() {
@Override
public RectBean createFromParcel(Parcel source) {
return new RectBean(source);
}
@Override
public RectBean[] newArray(int size) {
return new RectBean[size];
}
};
}
序列化方法和步驟
這是我們實作Parcelable接口的一系列方法。主要可以分為四步:
- 建立私有化構造方法(或者protected)
- 重寫describeContents方法。
- 重寫writeToParcel方法,這個方法是我們将對象序列化的方法。
- 實作Creator類,并實作createFromParcel方法和newArray方法,newArray方法不是很重要,主要看createFromParcel方法,這個方法是我們反序列化得到對象的方法。
這是幾個重要的方法:
方法 | 作用 |
describeContents | 傳回目前對象的描述,0或1(大多數情況都傳回0) |
writeToParcel(Parcel out,int flag) | 将目前對象寫入序列化結構,flag辨別有0或1,1時辨別目前對象作為傳回值傳回(大多數情況傳回0) |
createFromParcel(Parcel in) | 從序列化後的對象中建立原始對象 |
Parcel類
在這裡要介紹一下裡面出現很多的一個類——Parcel。這個類翻譯過來就是打包的意思,而我們序列化後的對象全都存儲在了這個裡面。
其實Parcelable類隻是一個外殼,而真正實作了序列化的,是Parcel這個類,它裡面有大量的方法,對各種資料進行序列化(下面我們會用到)。
以上是Parcel中的一部分方法(太多太多了)。
關于Parcel我們現在不用了解太多,我們隻需要知道是它實作序列化和反序列化功能就夠了。
不同類型屬性序列化操作
下面我們向RectBean中添加屬性,而這裡面屬性可以分為四種:
- 除boolean之外的基本類型(int、float、char、)
- boolean類型
- 對象類型
- 集合類型(List、。。。)
首先說一下,我們在通過Parcel類進行序列化和反序列化操作時,對應的不同類型的屬性,需要調用不同的方法,在上面我們也看到了,Parcel衆多類型的方法,就是為了對應不同類型的屬性。
但是看了這麼多方法,發現沒有關于boolean類型的方法。是以才把boolean單拿出來。
接下來我們一個一個的看:
1.除boolean外的基本類型
public class RectBean implements Parcelable {
private float x,y;
public RectBean() {
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(this.x);
dest.writeFloat(this.y);
}
protected RectBean(Parcel in) {
this.x = in.readFloat();
this.y = in.readFloat();
}
public static final Creator<RectBean> CREATOR = new Creator<RectBean>() {
@Override
public RectBean createFromParcel(Parcel source) {
return new RectBean(source);
}
@Override
public RectBean[] newArray(int size) {
return new RectBean[size];
}
};
}
我給RectBean類添加了兩個float的屬性:x、y。看一下哪些方法需要修改。
在writeToParcel中我們調用了Parcel的readFloat方法,将兩個屬性序列化,存儲到Parcel中。
然後在createFromParcel中,又通過readFloat中,将序列化的對象還原成原對象。
很簡單,沒有難度對吧,隻是兩個方法而已。
2.boolean類型
由于沒有boolean類型存儲,我們可以用byte或者int類型來進行存儲:
public class RectBean implements Parcelable {
private float x,y;
private boolean isVisible;
public RectBean() {
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(this.x);
dest.writeFloat(this.y);
dest.writeByte(this.isVisible ? (byte) 1 : (byte) 0);
}
protected RectBean(Parcel in) {
this.x = in.readFloat();
this.y = in.readFloat();
this.isVisible = in.readByte() != 0;
}
public static final Creator<RectBean> CREATOR = new Creator<RectBean>() {
@Override
public RectBean createFromParcel(Parcel source) {
return new RectBean(source);
}
@Override
public RectBean[] newArray(int size) {
return new RectBean[size];
}
};
}
通過一個條件運算符就解決了,讓true值為1,false值為0。
3.屬性為對象類型。
如果說上面的兩中都是基本類型,那麼這個是一個對象類型,怎麼解決?
首先第一步,我們添加的對象屬性對應的類,一定也要被序列化。這個應該很容易了解:
public class Point implements Parcelable {
protected Point(Parcel in) {
}
public static final Creator<Point> CREATOR = new Creator<Point>() {
@Override
public Point createFromParcel(Parcel in) {
return new Point(in);
}
@Override
public Point[] newArray(int size) {
return new Point[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
}
}
建立一個Point類,并讓它實作Parcelable接口。
public class RectBean implements Parcelable {
private float x,y;
private boolean isVisible;
private Point point;
public RectBean() {
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(this.x);
dest.writeFloat(this.y);
dest.writeByte(this.isVisible ? (byte) 1 : (byte) 0);
dest.writeParcelable(this.point, flags);
}
protected RectBean(Parcel in) {
this.x = in.readFloat();
this.y = in.readFloat();
this.isVisible = in.readByte() != 0;
this.point = in.readParcelable(Point.class.getClassLoader());
}
public static final Creator<RectBean> CREATOR = new Creator<RectBean>() {
@Override
public RectBean createFromParcel(Parcel source) {
return new RectBean(source);
}
@Override
public RectBean[] newArray(int size) {
return new RectBean[size];
}
};
}
然後在RectBean中添加Point類的屬性。這次調用的是Parcel的readParcelable方法和writeParcelable方法。但是在反序列化時候,需要用到屬性類的類加載器。
this.point = in.readParcelable(Point.class.getClassLoader());
4.集合類型的屬性。
集合類型的屬性切記,一定要初始化!!!!!
public class RectBean implements Parcelable {
private float x,y;
private boolean isVisible;
private Point point;
private List<Point> points = new ArrayList<>();
public RectBean() {
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(this.x);
dest.writeFloat(this.y);
dest.writeByte(this.isVisible ? (byte) 1 : (byte) 0);
dest.writeParcelable(this.point, flags);
dest.writeTypedList(this.points);
}
protected RectBean(Parcel in) {
this.x = in.readFloat();
this.y = in.readFloat();
this.isVisible = in.readByte() != 0;
this.point = in.readParcelable(Point.class.getClassLoader());
this.points = in.createTypedArrayList(Point.CREATOR);
}
public static final Creator<RectBean> CREATOR = new Creator<RectBean>() {
@Override
public RectBean createFromParcel(Parcel source) {
return new RectBean(source);
}
@Override
public RectBean[] newArray(int size) {
return new RectBean[size];
}
};
}
這裡需要說明一下:
我們如果說List存放的是String,那麼在序列化和反序列化對應的方法就是writeStringList和createStringArrayList:
dest.writeStringList(this.points);
.....
this.points = in.createStringArrayList();
如果是系統中自帶的類,那麼會調用writeList和readList方法,而在readList中也需要系統類的類加載器:
dest.writeList(this.points);
...
in.readList(this.points, Thread.class.getClassLoader());
而如果是自定義的類,那麼會調用writeTypedList和createTypedArrayList,在createTypedArrayList中将自定義類的CREATOR傳入。(說明該自定義類也需要序列化)
dest.writeTypedList(this.points);
...
this.points = in.createTypedArrayList(Point.CREATOR);
以上便是Parcelable的主要使用方法了。下面我們來對兩種序列化做一個總結:
Serializable和Parcelable總結
- 兩者都是實作序列化得接口,都可以用Intent傳遞資料。
- Serializable使用時會産生大量的臨時變量,進行IO操作頻繁,消耗比較大,但是實作方式簡單。
- Parcelable是Android提供輕量級方法,效率高,但是實作複雜。
- 一般在記憶體中序列畫傳遞時選用Parcelable。在裝置或網絡中傳遞選用Serializable。
- 無論是Serializable還是Parcelable,兩種内屬性隻要有對象,那麼對應對象的類一定也要實作序列化。
以上便是今天的全部内容,希望對大家有所幫助,喜歡的朋友希望多多支援一下,有不同意見或者有錯誤的地方希望大家留言評論,謝謝大家支援!!