序列化和打印流

一、序列化

1.1 概述

  • 通俗定义
    序列化(Serialization)是将对象的状态信息转化为可以存储或者传输的形式的过程,一般将一个对象存储到一个储存媒介,例如档案或记忆体缓冲等,在网络传输过程中,可以是字节或者XML等格式;而字节或者XML格式的可以还原成完全相等的对象,这个相反的过程又称为反序列化;

  • Java中定义
    在Java中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用此对象。但是,我们创建出来的这些对象都存在于JVM中的堆(heap)内存中,只有JVM处于运行状态的时候,这些对象才可能存在。一旦JVM停止,这些对象也就随之消失;
    但是在真实的应用场景中,我们需要将这些对象持久化下来,并且在需要的时候将对象重新读取出来,所以Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据对象的类型对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

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

1.2 序列化流【ObjectOutputStream】

java.io.ObjectOutputStream extends OutputStream

作用:将Java对象的原始数据类型写出到文件,实现对象的持久存储。

  • 构造方法 :
    ObjectOutputStream(OutputStream out) :创建写入指定 OutputStreamObjectOutputStream
    • 参数:
      OutputStream out :字节输出流
  • 特有的成员方法 :
    public final void writeObject(Object obj) :将指定的对象写入 ObjectOutputStream

1.3 序列化操作

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

    • 该类必须实现 java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出 NotSerializableException
    • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用 transient 关键字修饰。
    public class Person implements Serializable{
        private String name;
        //private static int age;
        private String address;
        private transient int age; // transient瞬态修饰成员,不会被序列化
    
        public Person() {
        }
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public Person(String name, String address, int age) {
            this.name = name;
            this.address = address;
            this.age = age;
        }
    	//省略get/set方法
    }
    

    注意:static关键字:静态关键字
    静态优先于非静态加载到内存中(静态优先于对象进入到内存中)
    被static修饰的成员变量也是不能被序列化的,序列化的都是对象

  2. 写出对象方法

    public final void writeObject (Object obj) : 将指定的对象写出。

    public class Demo01ObjectOutputStream {
        public static void main(String[] args) throws IOException {
            //1.创建ObjectOutputStream对象,构造方法中传递字节输出流
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("10_IO\\person.txt"));
            //2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
            oos.writeObject(new Person("小美女","南京",18));// 姓名,地址被序列化,年龄没有被序列化。
            //3.释放资源
            oos.close();
        }
    }
    

1.4 反序列化流 【ObjectInputStream】

java.io.ObjectInputStream extends InputStream

作用:将之前使用 ObjectOutputStream 序列化的原始数据恢复为对象。

  • 构造方法

    ObjectInputStream(InputStream in) :创建从指定 InputStream 读取的 ObjectInputStream

    • 参数:
      InputStream in:字节输入流
  • 特有的成员方法

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

  • 反序化的前提

    1. 类必须实现 Serializable
    2. 必须存在类对应的 class 文件

1.5 反序列化操作

反序列化操作一

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

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

    public class Demo02ObjectInputStream {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            //1.创建ObjectInputStream对象,构造方法中传递字节输入流
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("10_IO\\person.txt"));
            //2.使用ObjectInputStream对象中的方法readObject读取保存对象的文件
            Object o = ois.readObject();
            //3.释放资源
            ois.close();
            //4.使用读取出来的对象(打印)
            System.out.println(o);//Person{name='小美女', address='南京', age=0} 0 是int的默认值
            Person p = (Person)o;
            System.out.println(p.getName()+p.getAddress()+p.getAge());//小美女南京0
        }
    }
    

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

反序列化操作二

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

  • 该类的序列版本号与从流中读取的类描述符的版本号不匹配 【每次修改都会生产新的序列号】
  • 该类包含未知数据类型
  • 该类没有可访问的无参数构造方法

为了解决上述问题:Serializable 接口给需要序列化的类,提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。

public class Person implements Serializable{
    //加入系列化版本号
    private static final long serialVersionUID = 1L;
    private String name;
    //private static int age;
    private String address;
    private transient int age; // transient瞬态修饰成员,不会被序列化
	// 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
    public int pid; 
    
    public Person() {
    }

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

    public Person(String name, String address, int age) {
        this.name = name;
        this.address = address;
        this.age = age;
    }
	//省略get/set方法
}

1.6 练习:序列化集合

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

案例分析

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

案例实现

ublic class Demo03Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //1.定义一个存储Person对象的ArrayList集合
        ArrayList<Person> list = new ArrayList<>();
        //2.往ArrayList集合中存储Person对象
        list.add(new Person("张三",18));
        list.add(new Person("李四",19));
        list.add(new Person("王五",20));
        //3.创建一个序列化流ObjectOutputStream对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("10_IO\\list.txt"));
        //4.使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
        oos.writeObject(list);
        //5.创建一个反序列化ObjectInputStream对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("10_IO\\list.txt"));
        //6.使用ObjectInputStream对象中的方法readObject读取文件中保存的集合
        Object o = ois.readObject();
        //7.把Object类型的集合转换为ArrayList类型
        ArrayList<Person> list2 = (ArrayList<Person>)o;
        //8.遍历ArrayList集合
        for (Person p : list2) {
            System.out.println(p);
        }
        //9.释放资源
        ois.close();
        oos.close();
    }
}

二、打印流

2.1 概述

平时我们在控制台打印输出,是调用 print 方法和 println 方法完成的,这两个方法都来自于java.io.PrintStream类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。

2.2 PrintStream类

java.io.PrintStream extends OutputStream

  • PrintStream特点:

    1. 只负责数据的输出,不负责数据的读取
    2. 与其他输出流不同,PrintStream 永远不会抛出 IOException
    3. 有特有的方法,print,println
      • void print(任意类型的值)
      • ``void println(任意类型的值并换行)`
  • 继承自父类的成员方法

    • public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
    • public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
    • public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。
    • public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
    • public abstract void write(int b) :将指定的字节输出流。

    注意:
    如果使用继承自父类的 write 方法写数据,那么查看数据的时候会查询编码表 97->a
    如果使用自己特有的方法 print/println 方法写数据,写的数据原样输出 97->97

  • 构造方法 :

    • PrintStream(File file):输出的目的地是一个文件
    • PrintStream(OutputStream out):输出的目的地是一个字节输出流
    • PrintStream(String fileName):输出的目的地是一个文件路径
  • 代码演示

    public class Demo01PrintStream {
        public static void main(String[] args) throws FileNotFoundException {
            //System.out.println("HelloWorld");
    
            //创建打印流PrintStream对象,构造方法中绑定要输出的目的地
            PrintStream ps = new PrintStream("10_IO\\print.txt");
            //如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97->a
            ps.write(97); // a
            //如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97->97
            ps.println(97); // 97
            ps.println(8.8); // 8.8
            ps.println('a'); // a
            ps.println("HelloWorld"); // HelloWorld
            ps.println(true); // true
    
            //释放资源
            ps.close();
        }
    }
    

2.3 改变打印流向

System.out 就是PrintStream类型的,只不过它的流向是系统规定的,打印在控制台上。
我们还可以使用 System.setOut 方法改变输出语句的目的地,改为参数中传递的打印流的目的地
static void setOut(PrintStream out)
重新分配“标准”输出流。

public class Demo02PrintStream {
    public static void main(String[] args) throws FileNotFoundException {
        System.out.println("我是在控制台输出");

        PrintStream ps = new PrintStream("10_IO\\目的地是打印流.txt");
        System.setOut(ps);//把输出语句的目的地改变为打印流的目的地
        System.out.println("我在打印流的目的地中输出");

        ps.close();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江七7

感谢大佬的赏赐

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值