序列化

  • 能够使用序列化流写出对象到文件
  • 能够使用反序列化流读取文件到程序中

1. 概述

Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据对象的类型对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化对象的数据对象的类型对象中存储的数据信息,都可以用来在内存中创建对象。

看图理解序列化

在这里插入图片描述

在这里插入图片描述

2. ObjectOutputStream:序列化流

在这里插入图片描述

java.io.ObjectOutputStream:序列化流,将Java对象的原始数据类型写出到文件,实现对象的持久存储。

作用:把对象以流的方式写入到文件中保存

构造方法

  • public ObjectOutputStream(OutputStream out):创建写入指定OutputStreamObjectOutputStream

参数:OutputStream out:字节输出流

代码如下:

FileOutputStream fileOut = new FileOutputStream("employee.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);

特有的成员方法

  • void writeObject(Object obj) :将指定的对象写入 ObjectOutputStream

使用步骤

  1. 创建ObjectOutputStream对象,构造方法中传递字节输出流
  2. 使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
  3. 释放资源

注意

文件是二进制字节存储的,无法直接查看

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\person.txt"));

oos.writeObject(new Person("钢铁侠",18)); // 文件是二进制字节存储的,无法直接查看

oos.close();

结果:(文件中内容)

/�� sr "cn.luis.demo04.ObjectStream.Person        I ageL namet Ljava/lang/String;xp   t 	钢铁侠

前提

一个对象要想序列化,必须满足两个条件:

1. 该类必须实现java.io.Serializable 接口

Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException

2. 该类的所有属性必须是可序列化的

如果有一个属性不需要可序列化的,则该属性必须注明是【瞬态】的,使用transient 关键字修饰。

public class Employee implements java.io.Serializable {
    public String name;
    public String address;
    public transient int age; // transient瞬态修饰成员,不会被序列化
    public void addressCheck() {
      	System.out.println("Address  check : " + name + " -- " + address);
    }
}

方法演示

  • public final void writeObject (Object obj) : 将指定的对象写出。
public class SerializeDemo{
   	public static void main(String [] args)   {
    	Employee e = new Employee();
    	e.name = "zhangsan";
    	e.address = "beiqinglu";
    	e.age = 20; 
    	try {
      		// 创建序列化流对象
          ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));
        	// 写出对象
        	out.writeObject(e);
        	// 释放资源
        	out.close();
        	fileOut.close();
        	System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
        } catch(IOException i)   {
            i.printStackTrace();
        }
   	}
}
输出结果:
Serialized data is saved

3 ObjectInputStream类:反序列化流

在这里插入图片描述

ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

构造方法

  • public ObjectInputStream(InputStream in): 创建一个指定InputStreamObjectInputStream

参数:InputStream in:字节输入流

特有的成员方法

  • public final Object readObject():从 ObjectInputStream读取对象。

使用步骤

  1. 创建ObjectInputStream对象,构造方法中传递字节输入流
  2. 使用ObjectInputStream对象中的方法readObject读取保存对象的文件
  3. 释放资源
  4. 使用读取出来的对象(打印)

前提

  • 必须存在类对应的class文件
  • 类必须实现Serializable

3.5 反序列化操作

要点一

如果能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream读取对象的方法:

  • public final Object readObject () : 读取一个对象。
注意

对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常。

代码:

public class DeserializeDemo {
   public static void main(String [] args)   {
        Employee e = null;
        try {		
             // 创建反序列化流
             FileInputStream fileIn = new FileInputStream("employee.txt");
             ObjectInputStream in = new ObjectInputStream(fileIn);
             // 读取一个对象
             e = (Employee) in.readObject();
             // 释放资源
             in.close();
             fileIn.close();
        }catch(IOException i) {
             // 捕获其他异常
             i.printStackTrace();
             return;
        }catch(ClassNotFoundException c)  {
        	// 捕获类找不到异常
             System.out.println("Employee class not found");
             c.printStackTrace();
             return;
        }
        // 无异常,直接打印输出
        System.out.println("Name: " + e.name);	// zhangsan
        System.out.println("Address: " + e.address); // beiqinglu
        System.out.println("age: " + e.age); // 0
    }
}
要点二

JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。发生这个异常的原因如下:

  • 该类的序列版本号与从流中读取的类描述符的版本号不匹配
  • 该类包含未知数据类型
  • 该类没有可访问的无参数构造方法

在这里插入图片描述

问题:
  • 每次修改类的定义,都会给class文件生成一个新的序列号
解决方案:
  • 无论是否对类的定义进行修改,都不重新生成新的序列号
  • 可以手动给类添加一个序列号
格式:
  • 在Serializable接口规定:可序列化类可以通过声明名为“serialVersionUID”的字段
  • 该字段必须是静态(static)、最终(final)的long 型字段
  • 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
  static final long serialVersionUID = 1L; 常量不能改变

代码:

public class Employee implements java.io.Serializable {
     // 加入序列版本号
     private static final long serialVersionUID = 1L;
     public String name;
     public String address;
     // 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
     public int eid; 

     public void addressCheck() {
         System.out.println("Address  check : " + name + " -- " + address);
     }
}

案例:序列化集合

当我们想在文件中保存多个对象的时候,可以把多个对象存储到一个集合中,对集合进序列化和反序列化

  1. 将存有多个自定义对象的集合序列化操作,保存到list.txt文件中。
  2. 反序列化list.txt ,并遍历集合,打印对象信息。

案例分析

  1. 把若干学生对象 ,保存到集合中。
  2. 把集合序列化。
  3. 反序列化读取时,只需要读取一次,转换为集合类型。
  4. 遍历集合,可以打印所有的学生信息

步骤

  1. 定义一个存储Person对象的ArrayList集合
  2. 往ArrayList集合中存储Person对象
  3. 创建一个序列化流ObjectOutputStream对象
  4. 使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
  5. 创建一个反序列化ObjectInputStream对象
  6. 使用ObjectInputStream对象中的方法readObject读取文件中保存的集合
  7. 把Object类型的集合转换为ArrayList类型
  8. 遍历ArrayList集合
  9. 释放资源

案例实现

package cn.luis.oos;

import java.io.*;
import java.util.ArrayList;

/**
 * @ClassName SerTest
 * @Description TODO
 * @Author L
 * @Date 2020.06.27 11:36
 * @Version 1.0
 * @Remark
 **/
public class SerTest {
    public static void main(String[] args) throws Exception {
        // 创建序列化所需的对象
        Person student = new Person("老王", 18);
        Person student2 = new Person("老张", 19);
        Person student3 = new Person("老李", 30);

        ArrayList<Person> arrayList = new ArrayList<>();
        arrayList.add(student);
        arrayList.add(student2);
        arrayList.add(student3);

        System.out.println(arrayList);

        // 对象存储路径
        String path = "E:\\File\\ideaProjects\\Test\\IO\\src\\cn\\luis\\resource\\a.txt";

        // 序列化操作
        OOS(arrayList, path);

        // 反序列化
        OIS(path);
    }

    /**
     * 反序列化方法
     *
     * @throws Exception
     */
    private static void OIS(String path) {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(path));
            // 读取对象,强转为ArrayList类型
            ArrayList<Person> list = (ArrayList<Person>) ois.readObject();
            System.out.println("反序列化成功,正在打印对象属性..");

            for (int i = 0; i < list.size(); i++) {
                Person s = list.get(i);
                System.out.println(s.getName() + "--" + s.getAge());
            }
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("文件中没有序列化的对象...");
            e.printStackTrace();
        } finally {
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 序列化方法
     *
     * @param arrayList
     * @param path
     * @throws Exception
     */
    private static void OOS(ArrayList<Person> arrayList, String path) throws Exception {
        // 创建 序列化流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
        // 写出对象
        oos.writeObject(arrayList);
        System.out.println("序列化成功,点击文件查看...");
        System.out.println("==========");
        // 释放资源
        oos.close();
    }
}

结果:

[Person{name='老王', age=18}, Person{name='老张', age=19}, Person{name='老李', age=30}]
序列化成功,点击文件查看...
==========
反序列化成功,正在打印对象属性..
老王--18
老张--19
老李--30

Person实体类

package cn.luis.oos;

import java.io.Serializable;

public class Person implements Serializable{
    // 无论此类是否修改,序列号都固定不变
    private static final long serialVersionUID = 1L;
    private String name;
    //private static int age;
    private /*transient*/ int age;
//    public int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", 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;
    }
}

瞬态和静态

当一个类的对象需要被序列化时,某些属性不需要被序列化时,这时不需要序列化的属性可以使
用关键字 transient 修饰。

只要被 transient 修饰了,序列化时这个属性就不会被序列化了。

同时静态修饰也不会被序列化,因为序列化是把对象数据进行持久化存储,而静态的属于类加
载时的数据,不会被序列化。

静态

  • 静态优先于非静态加载到内存中(静态优先于对象进入到内存中)

  • 序列化的是对象,静态与对象无关!

瞬态

public class Person implements Serializable{
    // 无论此类是否修改,序列号都固定不变
    private static final long serialVersionUID = 1L;
    private String name;
    private transient int age;

结果:

[Person{name='老王', age=18}, Person{name='老张', age=19}, Person{name='老李', age=30}]
序列化成功,点击文件查看...
==========
反序列化成功,正在打印对象属性..
老王--0
老张--0
老李--0
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值