對象序列化
Java是一種完全面向對象的高級語言,所以在編寫程序的時候數據大都存放在對象當中。我們有時會需要將內存中的整個對象都寫入到文件中去,然後在適當的時候再從文件中將對象還原至內存。我們可以使用序列化,java.io.ObjectInputStream和java.io.ObjectOutputStream類來完成這個任務
什麼是對象的序列化(Serialize)?爲什麼要實現對象的序列化?
序列化是指將對象的狀態信息轉換爲可以存儲或傳輸的形式(2進制數據)的過程。在序列化期間,對象將其當前狀態寫入到臨時或持久性存儲區。以後,可以通過從存儲區中讀取或反序列化對象的狀態,重新創建該對象。
序列化的目的:
1. 永久地保存對象,保存對象的字節序列到本地文件中
2. 通過序列化對象在網絡中傳遞對象
3. 通過序列化對象在進程間傳遞對象
如何實現序列化
如果我們想要序列化一個對象,那麼這個對象必須實現Serializable接口。Serializable接口沒有任何的抽象方法,實現這個接口僅僅是爲了通知編譯器已這個對象將要被序列化,所以此接口僅僅是一個表示接口。類似的用法還有Cloneable接口,實現這個接口也只是起到通知編譯器的作用。
序列化的一般步驟:
1. 聲明對象輸出流
2. 聲明文件輸出流,並實例化
3. 用文件輸出流對象實例化對象輸出流
4. 調用對象輸出流的writeObject函數保存對象
5. 關閉對象輸出流
反序列化步驟:
1. 聲明對象輸入流
2. 聲明文件輸入流
3. 用文件輸入流對象實例化對象輸入流
4. 調用對象輸入流的readObject函數讀取對象,打印讀取到的對象內容
5. 關閉對象輸入流
transient關鍵字:
在序列化操作的時候,如果某個屬性不希望被序列化下來,則可以直接使用transient 關鍵字聲明。
對象的序列化和反序列化
想要完成對象的輸入輸出,還必須依靠ObjectInputStream和ObjectOutputStream;
使用對象輸出流輸出序列化對象的步驟,也稱爲序列化,而使用對象輸入流讀入對象的過程,也稱爲反序列化。
到底序列化了哪些東西呢?
所有的對象擁有各自的屬性值,但是所有的方法都是公共的,所以序列化對象的時候實際上序列化的就是屬性。
代碼實現
首先新建一個類實現Serializable接口,進行序列化: SerializableObject.java
package com.silion.androidproject.serializable;
import java.io.Serializable;
/**
* 實現Serializable接口,僅僅起標識這個類可被序列化的作用
*
* Created by silion on 2016/9/9.
*/
public class User implements Serializable {
// 可序列化對象的版本,進行序列化或者反序列化時,版本需要一致
private static final long serialVersionUID = -8284949931281996242L;
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
serialVersionUID
自動生成serialVersionUID:
Setting->Inspections->Serialization issues->Serializable class without ’serialVersionUID’
選中以上後,在你的class中:光標定位在類名前,按 Alt+Enter 就會提示自動創建 serialVersionUID 了
在對象進行序列化或者反序列化操作的時候,要考慮版本的問題,如果序列化的版本和反序列化的版本不統一則就可能造成異常,所以在序列化操作中引入了一個serialVersionUID的常量,可以通過此常量來驗證版本的一致性,在進行反序列化時,JVM會將傳過來的字節流中的serialVersionUID與本地相應實體的serialVersionUID進行比較,如果相同就認爲是一致的,可以進行反序列化,否則就拋出不一致的異常。
接下來實現佈局文件activity_serialize.xml,就3個按鈕:寫入對象,追加對象,讀出對象
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".serializable.SerializeActivity">
<Button
android:id="@+id/btWrite"
android:text="寫入對象"
android:textSize="22sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btAdd"
android:text="追加對象"
android:textSize="22sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btRead"
android:text="讀出對象"
android:textSize="22sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
最後在SerializeActivity實現對象的輸入輸出
package com.silion.androidproject.serializable;
import android.content.Context;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import com.silion.androidproject.R;
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;
import java.util.ArrayList;
import java.util.List;
public class SerializeActivity extends AppCompatActivity implements View.OnClickListener {
private User[] mUsers;
private User mAppendUser;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_serialize);
findViewById(R.id.btWrite).setOnClickListener(this);
findViewById(R.id.btAppend).setOnClickListener(this);
findViewById(R.id.btRead).setOnClickListener(this);
mUsers = new User[] {new User("silion", 1), new User("silion", 2)};
mAppendUser = new User("silion", 3);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btWrite: {
writeObject(mUsers, getDiskCacheDir(this, "UserSerializable"));
break;
}
case R.id.btAppend: {
appendObject(mAppendUser, getDiskCacheDir(this, "UserSerializable"));
break;
}
case R.id.btRead: {
List<Object> list = readObject(getDiskCacheDir(this, "UserSerializable"));
android.util.Log.d("silion", list.toString());
// for (Object object : list) {
// android.util.Log.d("silion", ((User) object).toString());
// }
break;
}
default:
break;
}
}
private List<Object> readObject(File file) {
List<Object> list = new ArrayList<>();
try(FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis)) {
while (fis.available() > 0) {
list.add(ois.readObject());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return list;
}
private void appendObject(Object o, String file) {
appendObject(o, new File(file));
}
private void appendObject(Object o, File file) {
/**
* true 以追加模式創建文件流對象
*/
try(FileOutputStream fos = new FileOutputStream(file, true);
ObjectOutputStream oos = new ObjectOutputStream(fos) {
@Override
protected void writeStreamHeader(){
// 重寫 writeStreamHeader()方法,空實現
}
}) {
// 寫入對象
oos.writeObject(o);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void writeObject(Object[] objs, String file) {
writeObject(objs, new File(file));
}
public void writeObject(Object[] objs, File file) {
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))) {
// 寫入對象
for(Object o : objs)
{
oos.writeObject(o);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public File getDiskCacheDir (Context context, String name) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + name);
}
}
注意,當我們想要向一個已經存在的文件中追加對象時,應該重寫ObjectOutputStream的writeStreamHeader()方法,並空實現。因爲,ObjectOutputStream在寫入數據的時候會加上一個特別的流頭(Stream Header),在讀取數據的時候會先檢查這個流頭。所以我們在向文件中追加對象的時候ObjectOutputStream就會再次向文件中寫入流頭,這樣在讀取對象的時候會發生StreamCorrupedException異常。
結果
1. 點擊寫入對象,寫入兩個User對象,點擊讀出對象,打印log如下
silion: [User{name='silion', age=1}, User{name='silion', age=2}]
- 點擊追加對象,追加一個User對象,點擊讀出對象,打印log如下
silion: [User{name='silion', age=1}, User{name='silion', age=2}, User{name='silion', age=3}]
更多關於序列化,如Externalizable、Serializable實現與Parcelabel實現請參考:
Java IO之對象的序列化、ObjectInputStream和ObjectOutputStream類
Intent傳遞對象的兩種方法Serializable 和 Parcelable