对象序列化
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