序列化与打印流

序列化

ObjectOutputStream类

java.io.ObjectOutputStream类,将java对象的原始数据类型写入到文件,实现对象的持久存储。
构造方法:
public ObjectOutputStream(OutputStream out):创建一个OutputStream的ObjectOutputStream。构造方法代码如下:

FileOutputStream fileout=new FileOutputStream("employee.txt");
ObjectOutputStream out=new ObjectOutputStream(fileout);
序列化操作
  1. 一个对象想要序列化,必须满足两个条件:
  • 该类必须实现java.io.Serializable接口,Serializable是一个标记接口,不实现此接口的类将不会使任何状态的序列化或反序列化,会抛出NotSerializableException
  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性 必须注明是瞬态的,使用transient关键字修饰。
import java.io.Serializable;

/**
 * 序列化和反序列化的时候,会抛出NotSerializableException没有序列化异常。
 * 类通过实现 java.io.Serializable 接口以启用器序列化功能,未实现此接口的类将无法使其任何状态序列化和反序列化。
 * Serializable也叫标记型接口
 *      要序列化和反序列化的类必须实现Serializable接口,就会给他添加一个标记
 *      当我们进行序列化和反序列化的时候,就会检测这个类上是否有Serializable标记,有的话就可以序列化和反序列化。没有就会报错会抛出NotSerializableException异常。
 */
public class Student implements Serializable {
    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() {
    }

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

    @Override
    public String toString() {
        return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}




import org.junit.Test;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

/**
 * java.io.ObjectOutputStream extends OutputStream
 * ObjectOutputStream:对象的序列化流
 * 作用:把对象以流的方式写入到文件中保存
 *
 * 构造方法:
 *      ObjectOutputStream(OutputStream out):创建写入指定OutputStream的ObjectOutputStream
 *      参数:
 *          OutputStream out:字节输出流。
 * 特有的成员方法:
 *      void writeObject(Object obj):将指定的对象写入ObjectOutputStream
 * 使用步骤:
 *      1.创建ObjectOutputStream对象,构造方法中传递字节输入流
 *      2.使用ObjectOutputStream对象中的writeObject,把对象写入到文件中
 *      3.释放资源
 */

public class DemoObjectStream01 {
    @Test
    public void testObjectOutputStream() throws IOException {
        //创建ObjectOutputStream对象,构造方法中传递字节输入流
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("student.txt"));
        //使用ObjectOutputStream对象中的writeObject,把对象写入到文件中
        objectOutputStream.writeObject(new Student("橙子",20));
        //释放资源
        objectOutputStream.close();
    }
}

ObjectInputStream类

ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复对象。
构造方法:
public ObjectInputStream(InputStream in):创建一个指定的InputStream的ObjectInputStream。

反序列化操作1

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

  • public final Object readObject():读取一个对象
package com.ft.first.objectStream;

import org.junit.Test;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

/**
 * java.io.ObjectInputStream extends InputStream
 * ObjectInputStream:对象反序列化流
 * 作用:把文件保存的对象,以流的方式读取出来使用
 *
 * 构造方法:
 *      ObjectInputStream(InputStream in):创建一个指定InputStream读取的ObjectInputStream
 *      参数:
 *          InputStream in:字节输入流。
 * 特有的方法:
 *      Object readObject():从ObjectInputStream读取对象
 * 使用步骤:
 *      1.创建ObjectInputStream对象,构造方法中传递字节输入流。
 *      2.使用ObjectInputStream对象的方法readObject读取保存对象的文件
 *      3.释放资源
 *      4.使用读取出来的对象(打印)
 * readObject()声明抛出了ClassNotFoundException(class文件找不到异常)
 * 当不存在对象的class文件时抛出此异常
 * 反序列化的前期:
 *      1.类必须实现Serializable
 *      2.必须存在类对应的class文件
 */
public class DemoObjectInputStream01 {
    @Test
    public void TestObjectInputStream() throws IOException, ClassNotFoundException {
        //创建ObjectInputStream对象,构造方法中传递字节输入流。
        ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("student.txt"));
        //使用ObjectInputStream对象的方法readObject读取保存对象的文件
        Object obj=objectInputStream.readObject();
        //释放资源
        objectInputStream.close();
        //使用读取出来的对象(打印)
        System.out.println(obj);
        Student student=(Student) obj;
        System.out.println(student.getName());
        System.out.println(student.getAge());

    }
}
关键字

static关键字:静态关键字。静态优先于非静态加载到内存中。被static修饰的成员变量不能被序列化的,序列化的都是对象。
transient关键字:瞬态关键字。
指定成员变量不想被序列化可以使用transient和static,transient跟static差不多只是没有static的含义。

package com.ft.first.objectStream;

import java.io.Serializable;

/**
 * 序列化和反序列化的时候,会抛出NotSerializableException没有序列化异常。
 * 类通过实现 java.io.Serializable 接口以启用器序列化功能,未实现此接口的类将无法使其任何状态序列化和反序列化。
 * Serializable也叫标记型接口
 *      要序列化和反序列化的类必须实现Serializable接口,就会给他添加一个标记
 *      当我们进行序列化和反序列化的时候,就会检测这个类上是否有Serializable标记,有的话就可以序列化和反序列化。没有就会报错会抛出NotSerializableException异常。
 *
 * static关键字:静态关键字。静态优先于非静态加载到内存中。被static修饰的成员变量不能被序列化的,序列化的都是对象。
 * private static int age;
 * 序列化写入:
 * objectOutputStream.writeObject(new Student("橙子",20));
 * 反序列化读取:
 * Object obj=objectInputStream.readObject();
 * 结果为:Student{name='橙子', age=0}
 *
 * transient关键字:瞬态关键字。
 * 被transient修饰的成员变量,不能被序列化
 * private transient int age;
 * 序列化写入:
 * objectOutputStream.writeObject(new Student("橙子",20));
 * 反序列化读取:
 * Object obj=objectInputStream.readObject();
 * 结果为:Student{name='橙子', age=0}
 */
public class Student implements Serializable {
    private String name;
    private transient 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() {
    }

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

    @Override
    public String toString() {
        return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}
反序列化操作2

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

  • 该类的序列化版本号与从流中读取的类描述符的版本号不配匹。
  • 该类包含未知数据类型。
  • 该类没有可访问的无参数构造方法
    Serializable接口给需要序列化的类,提供了一个序列版本号。serialVersionUID该版本号的目的在于验证序列化的对象和对应类是否版本配匹。
    问题:
    每次修改类的定义,都会给class文件生成一个新的序列号。
    解决方案:
    无论是否对类的定义是否进行修改,都不重新生成新的序列号。可以手动给类添加一个序列号。
    格式在Serialzable接口规定:
    可序列号类可以通过声明为"serialVersionUID"的字段(该字段必须是静态(static)、最终(final)的long型字段)显示声明其自己的serialVersionUID。
public class Student implements Serializable {
    //定义这个类的serialVersionUID=42L;   不会出现序列号冲突
    static final long serialVersionUID=42L;    //常量不能改变


    private String name;
    //private transient int age;
    private int age;

序列化集合

  1. 将存有多个自定义对象的集合序列化操作,保存到list.txt文件中。
  2. 反序列化list.txt,并遍历集合,打印对象信息。
案例分析
  1. 把若干个学生对象,保存到集合中。
  2. 把集合序列化。
  3. 反序列化读取时,只需要读取一次,转换为集合类型。
  4. 遍历集合,可以打印所有学生信息。
案例实现
import org.junit.Test;

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

/**
 * 练习:序列化集合
 * 当我们想在文件中保存多个对象的时候,可以把多个对象存储到一个集合中,对集合进行序列化和反序列化
 * 分析:
 * 1.定义一个存储Student对象的ArrayList集合
 * 2.往ArrayList集合中存储Student对象
 * 3.创建一个序列化流ObjectOutputStream对象
 * 4.使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
 * 5.创建一个反序列化ObjectInputStream对象
 * 6.使用ObjectInputStream对象中的方法readObject,读取文件中保存的集合
 * 7.把Object类型的集合转换为ArrayList类型
 * 8.遍历ArrayList集合
 * 9.释放资源
 */
public class DemoObjectInputStream02 {
    @Test
    public void testListStudent() throws IOException, ClassNotFoundException {
        //定义一个存储Student对象的ArrayList集合
        List list = new ArrayList();
        //往ArrayList集合中存储Student对象
        list.add(new Student("张三", 18));
        list.add(new Student("李四", 19));
        list.add(new Student("王五", 20));
        //创建一个序列化流ObjectOutputStream对象
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("listStudent.txt"));
        //使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
        objectOutputStream.writeObject(list);
        //创建一个反序列化ObjectInputStream对象
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("listStudent.txt"));
        //使用ObjectInputStream对象中的方法readObject,读取文件中保存的集合
        Object obj = objectInputStream.readObject();
        //把Object类型的集合转换为ArrayList类型
        ArrayList<Student> list1=(ArrayList<Student>)obj;
        //遍历ArrayList集合
        for(Student s:list1){
            System.out.println(s);
        }
        //释放资源
        objectInputStream.close();
        objectOutputStream.close();
    }
}

打印流

概述

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

System.out.println(" ");
PrintStream类

构造方法:

  • public PrintStream(String fileName):使用指定的文件名创建一个新的打印流。

构造举例,代码如下:

PrintStream ps=new PrintStream("ps.txt");

改变打印流方向
System.out就是PrintStream类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,我们就可以玩一个"小把戏",改变它的流向。

import org.junit.Test;

import java.io.FileNotFoundException;
import java.io.PrintStream;

/**
 * java.io.PrintStream:打印流
 * PrintStream 为其他输出流添加了功能,使他们能够方便地打印各种数据值表示形式。
 * PrintStream特点:
 *      1.只负责数据的输出,不负责数据的读取
 *      2.与其他输出流不同,PrintStream永远不会抛出IOException,但会抛出文件找不到的异常
 *      3.有特有的方法,print、println
 *          print:(输出任意的数据格式)
 *          println:(输出任意的数据格式带换行)
 * 构造方法:
 *      PrintStream(File file):输出的目的地是文件
 *      PrintStream(OutputStream out):输出的目的地是一个输出流
 *      PrintStream(String FileName):输出的目的地是一个文件路径
 * PrintStream extends OutputStream
 * 继承自父类的成员方法:
 *      void close():关闭此输出流并释放与此流有关的所有系统资源。
 *      void flush():刷新此输出流并强制写出所有缓冲的输出字节。
 *      void write(byte[] b):将 b.length 个字节从指定的 byte 数组写入此输出流。
 *      void write(byte[] b, int off, int len):将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
 *      abstract  void write(int b):将指定的字节写入此输出流。
 * 注意:
 *      如果使用继承自父类的write方法写数据,那么查询数据的时候会查询编码表。例:97->a
 *      如果使用它自己的方法print、println写数据,写的数据会原样输出.例:97->97
 */
public class OutStream {
    @Test
    public void testOut() throws FileNotFoundException {
        //创建打印流PrintStream对象,构造方法中绑定要输出的目的地
        PrintStream printStream=new PrintStream("print.txt");
        //如果使用继承自父类的write方法写数据,那么查询数据的时候会查询编码表。例:97->a
        printStream.write(97);
        //如果使用它自己的方法print、println写数据,写的数据会原样输出.例:97->97
        printStream.println(97);
        //释放资源
        printStream.close();
    }

    /**
     * 可以改变输出语句的目的地(打印的流向)
     * 输出语句,默认在控制台输出
     * 使用:System.setOut方法改变输出语句的目的地改为参数中传递的打印流的目的地。
     *      static void setOut(PrintStream out):重新分配"标准"输出流。
     */
    @Test
    public  void testOut01() throws FileNotFoundException {
        System.out.println("我是在控制台打印输出");
        PrintStream printStream=new PrintStream("打印流.txt");
        //把输出语句的目的地改变为打印流的目的地
        System.setOut(printStream);
        System.out.println("我在打印流的目的地中输出");
        printStream.close();
    }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容