对象的序列化概述
使用流的技术将对象直接保存到文件中的过程则称为对象的序列化。
实现对象序列化操作需要使用到的流:ObjectOutputStream
对象的反序列化概述
使用流的技术从文件中读取对象的过程则称为对象的反序列化。
实现对象反序列化操作需要使用到的流:ObjectInputStream
ObjectOutputStream类概述
继承OutputStream,是一个字节流。
ObjectOutputStream类构造方法
ObjectOutputStream(OutputStream out) - 根据字节输出流创建对象输出流。 - 可以传递的字节输出流:FileOutputStream,BufferedOutputStream
ObjectOutputStream类成员方法
void writeObject(Object obj); 将对象obj输出到流关联的目标文件中。
对象序列化的注意事项
对象所属的类要实现Serializable接口 java.io.NotSerializableException: 要序列化的对象所属于类没有实现Serializable接口时会抛出该异常。 Serializable接口是一个标记性接口。
public class ObjectOutputStreamDemo { public static void main(String[] args) throws Exception { // 创建学生对象 Student stu = new Student("jack",23,"男"); // 创建对象输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.txt")); // 保存学生对象到文件中 oos.writeObject(stu); // 关闭流 oos.close(); } } class Student implements Serializable{ /** * 指定一个固定的序列号。解决序列号冲突 */ private static final long serialVersionUID = 1L; private String name; private int age; private String sex; public String getName() { return name; } public void setName(String name) { this.name = name; } public Student() { super(); // TODO Auto-generated constructor stub } public Student(String name, int age, String sex) { super(); this.name = name; this.age = age; this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String toString() { return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]"; } }
ObjectInputStream流读取对象
ObjectInputStream类概述
继承 InputStream,是一个字节流。
ObjectInputStream类构造方法
ObjectInputStream(InputStream in) * 根据字节输入流创建对象输入流 * 可以传递的字节输入流:FileInputStream,BufferedInputStream
ObjectInputStream类成员方法
Object readObject(); 从流关联的文件中读取对象
注意事项
反序列化操作关联的目标文件一定是序列化操作时使用的目标文件。
public class ObjectInputStreamDemo { public static void main(String[] args) throws Exception { // 创建对象输入流 ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream("stu.txt"))); // 从流关联的文件中读取对象 Student stu = (Student) ois.readObject(); System.out.println(stu); // 关闭流 ois.close(); } }
transient关键字
transient关键字作用 * 用来修饰成员变量的,能够保证该成员变量的值不会被序列化到文件中。 transient使用格式 * 修饰符 transient 数据类型 变量名;
序列化中的序列号冲突问题
问题产生原因
当一个类实现Serializable接口后,创建对象并将对象写入文件,之后更改了源代码
(比如:将成员变量的修饰符有private改成public),更改源码后并没有再次将对象写入文件,又再次从文件中读取对象时会报异常。
解决序列号冲突
指定一个固定的序列号。
固定序列号的定义方式
private static final long serialVersionUID = 1478652478456L;
这样每次编译类时生成的serialVersionUID值都是固定的
自定义对象输出流
public class Student implements Serializable{ // 自定义序列号 private static final long serialVersionUID = 4799368385166961927L; private String name; private int 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; } public Student(String name, int age) { super(); this.name = name; this.age = age; } public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } } /** * 自定义对象输出流 * @author pkxing * */ public class MyObjectOutputStream extends ObjectOutputStream { public MyObjectOutputStream(OutputStream out) throws IOException { super(out); } /** * 重写该方法的目的 * 在第一次序列化对象时,需要写一个头部信息,以后追加序列化对象时不再输出头部信息。 */ protected void writeStreamHeader() throws IOException { System.out.println("进来了吗"); } /** * 根据目标文件对象创建对象输出流 * @param file * @return * @throws Exception */ public static ObjectOutputStream getObjectOutputStream(File file) throws Exception { // 对象输出流的引用变量 ObjectOutputStream oos = null; // 判断文件是否存在或长度是否为0 if(!file.exists() || file.length() == 0) { // 第一次输出对象到文件中,需求写头部信息 // 创建对象输出流 oos = new ObjectOutputStream(new FileOutputStream(file,true)); } else { // 不是第一次输出对象到文件中,不需要写头信息 // 创建自定义对象输出流对象 oos = new MyObjectOutputStream(new FileOutputStream(file,true)); } return oos; } } /** 为什么要自定义对象输出流? * 因为官方提供的对象输出流在每次保存对象时都会输出一个头部信息, 如果每次追加保存对象时要写一个头部信息则无法正确读取对象数据。 * 要想正确读取对象数据,则要求保存第一个对象需要写头部信息,而之后追加的对象不能写头部信息。 * 只能自定义对象输出流,重写写出头部信息的方法:writeStreamHeader方法。 自定义对象输出流的步骤 * 创建一个类继承ObjectOutputStream类 * 提供一个带参数的构造方法,重写writeStreamHeader方法,在该方法中什么都不做。 * 在序列化对象时需要程序猿自己判断是否是第一次保存对象 * 如果是则创建系统ObjectOutputStream类的对象来实现对象的序列化操作 * 如果不是,则创建自定义对象输出流类的对象来实现对象的序列化操作。 */ public class ObjectOutputStreamDemo { public static void main(String[] args) throws Exception { // 获得对象输出流 ObjectOutputStream oos = MyObjectOutputStream.getObjectOutputStream(new File("xxx.txt")); // 保存学生对象 oos.writeObject(new Student("jack",23)); // 关闭流 oos.close(); } public static void test02() throws Exception { // 创建文件对象 File file = new File("stu.txt"); // 对象输出流的引用变量 ObjectOutputStream oos = null; // 判断文件是否存在或长度是否为0 if(!file.exists() || file.length() == 0) { // 第一次输出对象到文件中,需求写头部信息 // 创建对象输出流 oos = new ObjectOutputStream(new FileOutputStream(file,true)); } else { // 不是第一次输出对象到文件中,不需要写头信息 // 创建自定义对象输出流对象 oos = new MyObjectOutputStream(new FileOutputStream(file,true)); } // 保存学生对象 oos.writeObject(new Student("jack",23)); // 关闭流 oos.close(); } }