項目實戰-對象序列化

對象序列化

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}]
  1. 點擊追加對象,追加一個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

發佈了41 篇原創文章 · 獲贊 8 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章