JavaIO ObjectOutputStream和ObjectInputStream 对象的序列化

对象序列化和反序列化的作用:

当创建对象时,程序运行时它就会存在,但是程序停止时,对象也就消失了.但是如果希望在程序不运行的情况下对象信息能依然存在,这样将对象重建并且拥有与程序上次运行时拥有的信息相同。这就是对象的序列化和反序列化。

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接口需要强制自定义序列化

序列化对象注意事项

  1. 对象的类名、属性都会被序列化;而方法、static属性(静态属性)、transient属性(即瞬态属性)都不会被序列化(这也就是第4条注意事项的原因)
  2. 虽然加static也能让某个属性不被序列化,但static不是这么用的
  3. 要序列化的对象的引用属性也必须是可序列化的,否则该对象不可序列化,除非以transient关键字修饰该属性使其不用序列化。
  4. 反序列化地象时必须有序列化对象生成的class文件(很多没有被序列化的数据需要从class文件获取)
  5. 当通过文件、网络来读取序列化后的对象时,必须按实际的写入顺序读取。


对象输入输出流要注意的细节


    1. 如果对象需要被写到文件上,那么对象所属的类必须实现Serializable接口Serializable接口没有任何方法,只是一个标识接口。
    2. 对象的反序列化创建对象的时候并不会调用到构造方法的。
    3. serialVersionUID是用于记录class文件的版本信息的,serialVersionUID这个数字是通过一个类的类名、成员、包名、工程名算出来的。
    4. 使用ObjectInputStream 反序列化的时候,ObjectInputStream会先读取文件中的serialVersionUID,然后与本地的class文件的serialVersionUID进行对比,如果这两个id不一致,那么反序列化就失败了。
    5. 如果序列化与反序列化的时候可能会修改类的成员,那么最好一开始就给这个类指定一个serialVersionUID,如果一个类制定了serialVersionUID,然后再序列化和反序列化的时候,jvm都不会再自己算这个serialVersionUID
    6. 如果一个对象某个数据不想被序列化到硬盘上,可以使用关键字transient修饰
    7. 如果一个类维护了另外一个类的引用,那么另外一个类也需要实现Serializable接口

综合了好多博客的内容已经有点乱了,知识点好乱的,以后再整理吧。

本博客广泛借鉴整理了网上其他博客,下面给出借鉴整理过的博客链接:

参考博客:

(21)IO流之对象的序列化和反序列化流ObjectOutputStream和ObjectInputStream

Java objectOutputStream的用法

(JAVA)从零开始之--对象输入输出流ObjectInputStream、ObjectOutputStream(对象序列化与反序列化)

ObjectInputStream和ObjectOutputStream

Java对象序列化详解

Java 对象序列化和反序列化

猜你喜欢

转载自blog.csdn.net/qq_21808961/article/details/80349643