Java-Day-26( 节点流和处理流 ( 序列化 + 对象流 + 标准输入输出流 + 转换流 + 打印流 ) )

发布时间 2023-06-04 15:29:05作者: 朱呀朱~

Java-Day-26

节点流和处理流

序列化和反序列化

  • 序列化就是在保存数据时,保存数据的值和数据类型
  • 反序列化就是在恢复数据时,恢复数据的值和数据类型
  • 需要让某个对象支持序列化机制,则必须让其类是可序列化的
    • 其类必须实现如下两个接口之一:
      • Serializable ( 标记接口:声明性质,没有任何方法 )
      • Externalizable ( 该接口有方法需要去实现,因此一般实现上面的接口 )

对象流 ObjectInputStream 和 ObjectOutputStream

  • 专门处理对象的处理流

需求引出

  • 将 int num = 100 这个 int 数据保存到文件中,注意不是 100 数字,而是 int 100 ( 连带数据类型 ),而且能够从文件中直接恢复 int 100 ( 而不是String 类型 )

    • 以前是只保存值,在过程中不保证数据类型不改变
  • 将 Dog dog = new Dog( " 小黄 ", 3 ) 这个 dog 对象保存到文件中,并能够从文件恢复 ( 反序列化为 Dog 对象 )

    • 保存值和数据类型,这样才能把小黄和 3 联系起来
  • 上面的要求就是,能够将基本数据类型或者对象进行序列化和反序列化操作

基本介绍

  • 提供了对基本类型或对象类型的序列化和反序列化的方法
  • ObjectOutputStream 提供序列化功能

image-20230530084221557

  • 可见仍是构造器引入 OutputStream 实现类
  • ObjectInputStream 提供反序列化功能

image-20230530084538251

练习

  • 使用 ObjectOutputStream 序列化基本数据类型和一个 Dog 对象 ( name, age ),并保存到 data.dat 文件中
    • 序列化后,保存的文件格式不是文本,而是按照他的格式来保存 ( 文本打开会乱码 )
    • 注意之前讲过的 Integer 等一系列包装类、String 就已实现了 Serializable 接口
public class Test {
    public static void main(String[] args) throws Exception {
        String filePath = "e:\\data.dat";
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
//        序列化数据到 filePath
        oos.writeInt(100); // int —> Integer (实现了 Serializable)
        oos.writeBoolean(true); // boolean —> Boolean (实现了 Serializable)
        oos.writeChar('a'); // char —> Character...
        oos.writeDouble(9.5); // double —> Double...
        oos.writeUTF("zyz哇");
//        存放字符串的方法:writerUTF,String本就实现了 Serializable
        oos.writeObject(new Dog("土豆", 10));

        oos.close();
        System.out.println("数据保存完毕 —> 序列化形式");
    }
}

// 如果需要序列化某个类的对象,必须实现接口
class Dog implements Serializable {
    private String name;
    private int age;

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 使用 ObjectInputStream 读取 data.dat 并反序列化恢复数据、

    • 如果我们希望调用 Dog 的方法,需要向下转型 ( 存是 writeObject(),获取是 readObject() )
    • 需要我们将 Dog 类的定义拷贝到都可以引用的位置,做成公共的包,即做到类要统一
    • 若是读取的时候更改了 Dog,就要再写入 Output ( 会顺带 Dog 的包路径 ) 一次,才能再读取 Input
    public class Test {
        public static void main(String[] args) throws Exception {
            String filePath = "e:\\data.dat";
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
    //        读取:
    //        读取的(反序列化)的顺序必须要和你保存数据(序列化)的顺序一致
    //        否则异常
            System.out.println(ois.readInt());
            System.out.println(ois.readBoolean());
            System.out.println(ois.readChar());
            System.out.println(ois.readDouble());
            System.out.println(ois.readUTF());
            System.out.println(ois.readObject()); // 底层Object —> Dog
    //        关闭外层流
            ois.close();
            System.out.println("数据取出完毕 —> 反序列化形式");
        }
    }
    
    
    // 此处的Dog为公共类,Input和Output的都是统一路径!才能不报错调用类中的方法!
    class Dog implements Serializable {
        private String name;
        private int age;
    
        public Dog(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Dog{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    • Dog为公共类,Input和Output的都是统一路径!才能不报错调用类中的方法!

注意细节

  • 读写顺序要一致 ( 顺序但凡不同便报错 )

  • 要求实现序列化或反序列化对象,需要实现 Serializable

  • 序列化的类中建议添加 SerialVersionUID,为了提高版本的兼容性

    • 如:

      class Dog implements Serializable {
          private String name;
          private int age;
      //    序列化的版本号,可以提高兼容性
          private static final long serialVersionUID = 1L;
      //    这样的话,再添加一个private的属性时,就不会认为是一个新的类,而是一个新的版本(升级版)
      //    .....
      }
      
  • 序列化对象时,默认将里面所有属性都进行序列化,但除了 static 或 transient ( 短暂的 ) 修饰的成员以外

    • 即,static 和 transient 修饰的属性值不会序列化成功 ( 反序列的时候没有值,是默认的 null )
  • 序列化对象时,要求里面属性的类型也要实现序列化接口

    • 例如 Dog 类中的 name 是 String 类型的 ( String 本身就实现了 Serializable 接口 ) 就可以序列化,若是添加一个新的类作为 Dog 属性,这个类要是不实现 Serializable 接口,那就会在序列化时报错
  • 序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化

    • 如:Integer 间接实现了序列化,继承的类才是直接实现序列化的

    image-20230530110016310

标准输入输出流

介绍

编译类型 默认设备
System.in 标准输入 InputStream 键盘
System.out 标准输出 PrintStream 显示器
  • in:System里 public final static InputStream in = null;

    • 编译类型:InputStream

    • 运行类型 getClass :BufferedInputStream ( 字节处理流 )

    • 常配 Scanner(),scanner.next() 到键盘得到输入

  • out:System里 public final static PrintStream out = null;

    • 翻译类型:PrintStream
    • 运行类型:PrintStream

转换流 InputStreamReader 和 OutputStreamWriter

引出

  • 实则将字节流转换成字符流
String filePath = "e:\\note.txt";
        BufferedReader br = new BufferedReader(new FileReader(filePath));
//        默认情况下读取文件是按照utf-8编码来,若是另存为时更换编码,换成别的后输出就是乱码

        String s = br.readLine();
        System.out.println("读取到的文件" + s); // 编码方式同,不会出现乱码
        br.close();
  • 所以去指定文件的编码方式,所以用到转换流,好去指定字节流的编码方式

介绍

  • InputStreamReader:Reader 的子类,可以将 InputStream ( 字节流 ) 包装 ( 转换 ) 成 Reader ( 字符流 )

    • 包装 ( 转换 ):即用的仍是字节流,只不过处理时按照字符方式来处理

    image-20230530113047553

    • 重点所用构造器:可以传入一个 InputStream 对象,然后用 Charset 用来指定处理的编码方式
  • OutputStreamWriter:Writer 的子类,实现将 OutputStream ( 字节流 ) 包装成 Writer ( 字符流 )

image-20230530114019579

  • 当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
  • 可以在使用时指定编码格式 ( 如:utf-8, gbk, ISO8859-1 等 )

练习

  • 编程将字节流 FileInputStream 包装 ( 转换 ) 成字符流 InputStreamReader,对文件进行读取 ( 按照 utf-8 / gbk 格式 ),进而再包装成 BufferedReader

    • 有点 gbk 还是乱码,那文件就是 utf-8 编码
    public class Test {
        public static void main(String[] args) throws IOException {
            String filePath = "e:\\note.txt";
    //        FileInputStream 转成了 InputStreamReader
    
    
    ////        指定编码 gbk / utf-8 (utf8)
    //        InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "utf-8");
    ////        BufferedReader包装流(处理)
    //        BufferedReader br = new BufferedReader(isr);
    
    //        一般常用的就把上两部一起写
            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "utf-8"));
    
            String s = br.readLine();
            System.out.println("读取所得 " + s);
    //        关闭外层流
            br.close();
        }
    }
    
  • 编程将字节流 FileOutputStream 包装 ( 转换 ) 成字符流OutputStreamWriter,对文件进行写入 ( 按照 utf-8 / gbk 格式 )

    public class Test {
        public static void main(String[] args) throws IOException {
            String filePath = "e:\\note.txt";
            String charSer = "utf8";
            OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath, true), charSer);
    //        勿忘不想覆盖就加 true
            osw.write("wa老朱呀");
    //        关闭外层流
            osw.close();
            System.out.println("按照 " + charSer + " 编码方式保存文件成功");
        }
    }
    

打印流 PrintStream 和 PrintWriter

  • 打印流只有输出流,没有输入流

PrintStream 字节打印流

  • 构造器可直接传文件路径 ( String ) 或文件 ( File )、OutputStream

image-20230603165712798

  • 练习

    public class Test {
        public static void main(String[] args) throws IOException {
    //        System.out 实际就是 public final static PrintStream out = null;
            PrintStream out = System.out;
    //        默认情况下,PrintStream 输出数据的位置是标准输出,即显示器(控制台里)
            /*
            public void print(String s) {
                if (s == null) {
                    s = "null";
                }
                write(s);  // 真正打印输出所在,所有也可以直接用 write
            }
            */
    //        out.print("wawa朱");
            out.write("wawa朱也".getBytes()); // 把字符串转成字节数组
            out.close();
    
    //        构造器可见,可以去修改打印流输出的位置/设备
    //        修改到 e:\f1.txt 文件中了
            System.setOut(new PrintStream("e:\\f1.txt"));
    //        显示器上就不可见内容了
            System.out.println("哈罗呀,zyz~");
        }
    }
    

PrintWriter 字符打印流

  • 构造器可传 Writer,OutputStream,String,File ( Chatset )

image-20230603165842059

  • 练习

    public class Test {
        public static void main(String[] args) throws IOException {
    //        out是PrintStream,其继承FilterOutputStream,其继承FilterOutputStream再继承着OutputStream
    //        此处传的是一个标准输出
    //        PrintWriter printWriter = new PrintWriter(System.out);
    
    //        想让输出位置在文件里
    //        FileWriter:super(new FileOutputStream(fileName));
    //        super:OutputStreamWriter (Writer)
            PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt"));
    
            printWriter.print("hi,你吼哇~");
            printWriter.close(); // flush + 关闭流。(不写就不会刷新写入)
        }
    }