项目实战-对象序列化

对象序列化

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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章