对象序列化和反序列化的作用:
当创建对象时,程序运行时它就会存在,但是程序停止时,对象也就消失了.但是如果希望在程序不运行的情况下对象信息能依然存在,这样将对象重建并且拥有与程序上次运行时拥有的信息相同。这就是对象的序列化和反序列化。
ObjectOutputStreamAPI:
ObjectOutputStream 可以将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。
只能将支持 java.io.Serializable 接口的对象写入流中。每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包。
writeObject 方法用于将对象写入流中。所有对象(包括 String 和数组)都可以通过 writeObject 写入。可将多个对象或基元写入流中。必须使用与写入对象时相同的类型和顺序从相应 ObjectInputstream 中读回对象。
还可以使用 DataOutput 中的适当方法将基本数据类型写入流中。还可以使用 writeUTF 方法写入字符串。
对象的默认序列化机制写入的内容是:对象的类,类签名,以及非瞬态和非静态字段的值。其他对象的引用(瞬态和静态字段除外)也会导致写入那些对象。可使用引用共享机制对单个对象的多个引用进行编码,这样即可将对象的图形恢复为最初写入它们时的形状。
writeObject 方法负责写入特定类的对象状态,以便相应的 readObject 方法可以恢复它。该方法本身不必与属于对象的超类或子类的状态有关。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。
序列化操作不写出没有实现 java.io.Serializable 接口的任何对象的字段。不可序列化的 Object 的子类可以是可序列化的。在此情况下,不可序列化的类必须有一个无参数构造方法,以便允许初始化其字段。在此情况下,子类负责保存和恢复不可序列化的类的状态。经常出现的情况是,该类的字段是可访问的(public、package 或 protected),或者存在可用来恢复状态的 get 和 set 方法。
在 writeObject 和 readObject 方法的实现中抛出 NotSerializableException,可以阻止对象的序列化。ObjectOutputStream 将捕获异常并中止序列化进程。
基本的序列化
基本的序列化由两个方法产生:一个方法用于序列化对象并将它们写入一个流,另一个方法用于读取流并反序列化对象:
ObjectOutputStream的写对象方法:
void writeObject(Object obj)
将指定的对象写入 ObjectOutputStream。
ObjectInputStream的读对象方法:
Object readObject()
从 ObjectInputStream 读取对象。
代码测试:默认序列化和默认反序列化
(1)要序列化的对象类:
//要序列化必须实现Serializable接口作为标志,虽然这个接口什么都没有
class Student implements Serializable
{
private String id;
private String name;
private String sex;
private int age;
public String getId()
{
return id;
}
public void setId(String id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getSex()
{
return sex;
}
public void setSex(String sex)
{
this.sex = sex;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public Student(String id, String name, String sex, int age)
{
super();
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
}
@Override
public String toString()
{
return "Student [id=" + id + ", name=" + name + ", sex=" + sex
+ ", age=" + age + "]";
}
}
(2)序列化和反序列化:
public class ObjectOutputInputStreamTest
{
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException
{
String packagePath=FilePath.getSrcPackagePath(ObjectOutputInputStreamTest.class);
String stuFile=packagePath+"student.txt";
//序列化
ObjectOutputStream objecOut=new ObjectOutputStream(
new FileOutputStream(stuFile)
);
Student student=new Student("B1000", "小明", "男", 21);
objecOut.writeObject(student);//序列化student对象
objecOut.close();
//----------------------------------------------------------------
//反序列化:
ObjectInputStream objectIn=new ObjectInputStream(
new FileInputStream(stuFile)
);
Student student2=(Student)objectIn.readObject();
System.out.println(student2);
objectIn.close();
}
}
运行结果:
Student [id=B1000, name=小明, sex=男, age=21]
student.txt:
控制台上打印成功说明已经成功的进行序列化和反序列化了。
序列化整个集合:
可以把上面的学生对象放入集合ArrayList中,然后再序列整个集合。使得可以保存成批的对象信息。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import com.lan.filepath.FilePath;
//测试集合的序列化
public class ArrayListTest
{
public static void main(String[] args) throws IOException, ClassNotFoundException
{
String packagePath=FilePath.getSrcPackagePath(ArrayListTest.class);
String stuFile=packagePath+"studentList.txt";
//序列化整个集合:
ObjectOutputStream objectOut=new ObjectOutputStream(
new FileOutputStream(stuFile)
);
ArrayList<Student> list=new ArrayList<Student>();
Student stu1=new Student("B1000", "张三", "男", 20);
Student stu2=new Student("B1001", "李四", "男", 20);
list.add(stu1);
list.add(stu2);
objectOut.writeObject(list);//序列化整个集合
objectOut.close();
//--------------------------------------------
//反序列化集合:
list.clear();//清空整个集合
ObjectInputStream objectIn=new ObjectInputStream(
new FileInputStream(stuFile)
);
list=(ArrayList<Student>) objectIn.readObject();//读取整个序列的集合到空集合中
//遍历打印集合
for (Student student : list)
{
System.out.println(student);
}
}
}
运行结果:
Student [id=B1000, name=张三, sex=男, age=20]
Student [id=B1001, name=李四, sex=男, age=20]
可以看到使用writerObject()和readObject()来进行序列化和反序列化时,使用方法都一样,只要传入对象就行,这个对象可以是普通的对象,也可是是集合这种存放许多对象的对象。
总结:
对象的输入输出流 : 主要的作用是用于写入对象信息与读取对象信息。 对象信息一旦写到文件上那么对象的信息就可以做到持久化了
对象的输出流: ObjectOutputStream
对象的输入流 : ObjectInputStream
使用:
对象的输出流将指定的对象写入到流(文件等)的过程,也就是将对象序列化的过程,
对象的输入流将指定序列化好的对象从流(文件等)读出来的过程,就是对象反序列化的过程。
要序列化对象,对象所对应的类必须要实现Serializable接口。(Serializable接口没有任何的方法,只是作为一个标识接口存在)。
选择序列化
transient
当对某个对象进行序列化时,系统会自动将该对象的所有属性依次进行序列化,如果某个属性引用到别一个对象,则被引用的对象也会被序列化。如果被引用的对象的属性也引用了其他对象,则被引用的对象也会被序列化。 这就是递归序列化。
有时候,我们并不希望出现递归序列化,或是某个存敏感信息(如银行密码)的属性不被序列化,我们就可通过transient关键字修饰该属性来阻止被序列化。
Externalizable接口
Externalizable接口 与Serializable 接口类似,只是Externalizable接口需要强制自定义序列化。
序列化对象注意事项
- 对象的类名、属性都会被序列化;而方法、static属性(静态属性)、transient属性(即瞬态属性)都不会被序列化(这也就是第4条注意事项的原因)
- 虽然加static也能让某个属性不被序列化,但static不是这么用的
- 要序列化的对象的引用属性也必须是可序列化的,否则该对象不可序列化,除非以transient关键字修饰该属性使其不用序列化。
- 反序列化地象时必须有序列化对象生成的class文件(很多没有被序列化的数据需要从class文件获取)
- 当通过文件、网络来读取序列化后的对象时,必须按实际的写入顺序读取。
对象输入输出流要注意的细节
-
- 如果对象需要被写到文件上,那么对象所属的类必须实现Serializable接口,Serializable接口没有任何方法,只是一个标识接口。
- 对象的反序列化创建对象的时候并不会调用到构造方法的。
- serialVersionUID是用于记录class文件的版本信息的,serialVersionUID这个数字是通过一个类的类名、成员、包名、工程名算出来的。
- 使用ObjectInputStream 反序列化的时候,ObjectInputStream会先读取文件中的serialVersionUID,然后与本地的class文件的serialVersionUID进行对比,如果这两个id不一致,那么反序列化就失败了。
- 如果序列化与反序列化的时候可能会修改类的成员,那么最好一开始就给这个类指定一个serialVersionUID,如果一个类制定了serialVersionUID,然后再序列化和反序列化的时候,jvm都不会再自己算这个serialVersionUID
- 如果一个对象某个数据不想被序列化到硬盘上,可以使用关键字transient修饰
- 如果一个类维护了另外一个类的引用,那么另外一个类也需要实现Serializable接口
综合了好多博客的内容已经有点乱了,知识点好乱的,以后再整理吧。
本博客广泛借鉴整理了网上其他博客,下面给出借鉴整理过的博客链接:
参考博客:
(21)IO流之对象的序列化和反序列化流ObjectOutputStream和ObjectInputStream
(JAVA)从零开始之--对象输入输出流ObjectInputStream、ObjectOutputStream(对象序列化与反序列化)