天天看點

Android中序列化與反序列化序列化概念和應用Serializable類Parcelable類

這幾天在看到設計模式時,看到有關于序列化的知識,發現自己之前這塊知識很欠缺,是以這花了兩天仔細研究了一下,感覺這個東西還是很有趣的(當然也很有用-。+),今天給大家帶來這篇文章,希望對大家有幫助。

序列化概念和應用

首先我們需要知道序列化是個什麼意思。

  • 序列化:将對象轉換為可傳輸的二進制流的過程。
  • 反序列化:把位元組序列恢複成對象的過程。

我舉個栗子:我們都進行過檔案操作吧。我們在檔案中寫數字,或者寫漢字,再或者寫英文,這些東西都是可以直接轉為二進制位元組的,我們把需要的資料存儲在File中,實作了資料存儲到裝置中,實作了資料持久化。

但是現在我有一個對象需要暫時存儲到檔案中,這個跟剛才的基本類型不一樣了哦。是以說怎麼辦?

還有這個情況:我們在進行網絡傳輸,傳輸賬号、密碼等,這些都是基本資料。那我如果想傳輸一個對象過去呢?到了對方電腦上之後,鬼知道你這是個什麼東西。。。。

根據這兩種案例,相信大家已經對序列化的應用有所了解了,主要可以分為以下幾類:

  1. 存儲到檔案或本地資料庫(如SQLite)這類持久化資料操作。
  2. 通過網絡請求傳輸對象。
  3. 記憶體中資料調用(Intent,下面會多次用到)。

一句話概括,就是對象進行存儲和傳遞的一種方法:轉化為二進制流。

而進行序列化主要有兩個重要的類:Serializable類和Parcelable類。我們分别來看一下。

Serializable類

首先說一下這個類,相信在Java中大家可能已經見到過這個類了。這裡簡單介紹一下:

public interface Serializable {
}

           

Serializable是一個标記接口,從上面的類中也可以看出來,他是一個空的接口。

我們傳輸時候,各種各樣的類,我們需要有一個類來對他們進行統一。但是Java中是單根繼承,而接口恰好彌補了這個地方。我們可以将實作的借口看做父類,這樣通過Serializable類就實作了多種類的統一,同時為類打上了标簽,可以通過instanceof Serializable判斷是否能序列化(個人了解)。

而它使用起來也很簡單,隻将需要序列化的類實作Serializable類就好了,其他的我們完全不用操作。

Serializable在Java中就有了,我們先來看一下在Java中它是如何使用:

Java中使用Serializable

Android中序列化與反序列化序列化概念和應用Serializable類Parcelable類

盜取了一張大神的圖,我們在學習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接口的一系列方法。主要可以分為四步:

  1. 建立私有化構造方法(或者protected)
  2. 重寫describeContents方法。
  3. 重寫writeToParcel方法,這個方法是我們将對象序列化的方法。
  4. 實作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總結

  1. 兩者都是實作序列化得接口,都可以用Intent傳遞資料。
  2. Serializable使用時會産生大量的臨時變量,進行IO操作頻繁,消耗比較大,但是實作方式簡單。
  3. Parcelable是Android提供輕量級方法,效率高,但是實作複雜。
  4. 一般在記憶體中序列畫傳遞時選用Parcelable。在裝置或網絡中傳遞選用Serializable。
  5. 無論是Serializable還是Parcelable,兩種内屬性隻要有對象,那麼對應對象的類一定也要實作序列化。

以上便是今天的全部内容,希望對大家有所幫助,喜歡的朋友希望多多支援一下,有不同意見或者有錯誤的地方希望大家留言評論,謝謝大家支援!!

繼續閱讀