1. IO流

1658995030403

1.1. 介绍

  • IO:输入/输出(Input/Output)
  • 流:指数据,也可以叫数据流
  • IO流就是用来处理设备间数据传输问题的.常见的应用: 文件复制; 文件上传; 文件下载,音视频播放

1.2. IO流的分类

  • 按照数据的流向

    • 输入流:读数据, 把数据从其他设备上读取到内存中的流。
    • 输出流:写数据, 把数据从内存中写出到其他设备上的流。
  • 按照数据类型来分

    • 字节流 :以字节为单位,读写数据的流。
      • 1G>1024M 1M-1024Kb 1Kb = 1024字节byte 1字节 = 8bit比特位
      • utf-8 汉字3字节 英文1字节
      • 字节输入流
      • 字节输出流
    • 字符流 :以字符为单位,读写数据的流 "abc打分"

      • 字符输入流

      • 字符输出流

    1658962185835

【IO流的使用场景注意】

  • 如果操作的是纯文本文件,优先使用字符流, 能用记事本打开看的懂的就是纯文本文件。
  • 如果操作的是图片、视频、音频等二进制文件,优先使用字节流
  • 如果不确定文件类型,优先使用字节流.字节流是万能的流

2.字节输出流 OutputStream

  • OutputStream: 抽象类,表示字节输出流的所有类的超类

2.1. 实用子类: FileOutputStream类

  • 构造:
    • public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。
    • public FileOutputStream(String name): 创建文件输出流以指定的路径写入文件。
  • 当你创建一个流对象时,必须传入一个文件或者文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,创建一个新的空白文件覆盖已有的文件。

2.2. 使用的三步

  • 1创建字节输出流对象
  • 2调用字节输出流对象的写数据方法
  • 3释放资源

2.3. 使用演示

//1创建FileOutputStream对象
//如果文件不存在 会被创建
//如果文件存在 会被新的文件覆盖
FileOutputStream fos = new FileOutputStream("module01/aaa/b.txt");

//2写内容
// 输入的数字 会被转为码表中的字母
fos.write(99);//c
fos.write(70);//F
fos.write(50);//2

//3关闭流  把文件资源释放
fos.close();

2.4. 注意:

  • 1.如果文件不存在,会帮我们创建 如果文件存在,会清空之前的内容
  • 2.输出的整数,实际是码表中的字母
  • 3 每次使用完流 一定要关闭流,释放资源

2.5. 一次写多个数据

  • 写数据的方法分类
方法名 说明
void write(int b) 将指定的字节写入此文件输出流 一次写一个字节数据
void write(byte[] b) 将 b.length字节从指定的字节数组写入此文件输出流 一次写一个字节数组数据
void write(byte[] b, int off, int len) 将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流 一次写一个字节数组的部分数据

代码

        //1构造 接收一个文件路径 或者一个文件对象
        FileOutputStream fos = new FileOutputStream("ddd.txt");

        //2 写内容
        fos.write(100);
        fos.write(101);
        fos.write(102);
        fos.write(103);
        fos.write(104);

        //可以直接写入一个byte数组
        fos.write(new byte[]{66, 67, 68, 69, 70});

        //把字符串转为byte数组 输出到文件
        fos.write("你好fdsfs".getBytes());

        //write(byte b[], int off, int len)
        //off偏移量 表示跳过几个数据
        //len数量 表示写入几个数据
        byte[] bs = {71, 72, 73, 74, 75};
        //把bs数组 偏移一个 然后写到文件里3个字节 这里是72 73 74写到文件里了
        fos.write(bs, 1, 3);

        //3 关闭流 释放文件
        fos.close();

【注意】

  • 如果要写入字符串,就把字符串通过getBytes()方法转为byte数组

2.6. 写入汉字

  • 用汉字的字符串转字节数组

    fos.write("你好吗哈哈哈".getBytes());

2.7. 字节流写数据如何实现换行

  • windows:\r\n
  • linux:\n
  • mac:\r

2.8. 字节流写数据如何实现追加写入

  • public FileOutputStream(String name,boolean append)
  • 创建文件输出流以指定的名称写入文件。如果第二个参数为true ,则字节将写入文件的末尾而不是开头

代码

//第二个参数为true 表示从后面追加写入数据
FileOutputStream fos = new FileOutputStream("module01/aaa/c.txt",true);

for (int i = 0; i < 5; i++) {
    fos.write("你好吗哈哈哈".getBytes());
    //回车   换行
    fos.write("\r\n".getBytes());
}

//3关闭流  把文件资源释放
fos.close();

3. 字节输入流 Inputstream

3.1. 构造方法

  • FileInputStream(File file): 创建一个 FileInputStream ,该文件由文件系统中的文件对象 file命名。
  • FileInputStream(String name): 创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
  • 当你创建一个流对象时,必须传入一个文件或者文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException

3.2. 其它方法

  • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
  • public abstract int read(): 从输入流读取数据的下一个字节。
  • public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 ,返回读取的长度,如果没有数据,返回-1。

3.2.1. 读取一个字节的代码

    public static void main(String[] args) throws IOException {
        //1创建对象
        FileInputStream fis = new FileInputStream("module01/bbb/a.txt");
        //2读取数据 一次读取一个字节
        int read = fis.read();
        System.out.println(read);

        //3关闭流
        fis.close();
    }

3.2.2. 读取多个字节到数组的代码

    public static void main(String[] args) throws IOException {
        //1创建对象
        FileInputStream fis = new FileInputStream("module01/bbb/a.txt");

        byte[] bs = new byte[10];
        //2读取数据到数组中,读取后会把字母转回编码的整数  返回读取的长度
        //这里数组长度为10 一次就读取了10个字节
        int result = fis.read(bs);
        System.out.println(result);//10
        System.out.println(Arrays.toString(bs));//[99, 115, 97, 100, 102, 115, 97, 100, 106, 108]

        //3关闭流
        fis.close();
    }

【注意】

  • 1创建 FileInputStream对象时 文件不存在 报错
  • 2读取的是编码数字,需要转为char

3.3. 循环读取一个文件的内容

  • public String(byte bytes[], int offset, int length)
    • 参1 表示要被转换的字节数组
    • 参2表示从头偏移多少个
    • 参3表示 取几个数据转为字符串
  • fis.read(bt) 读取内容到数组,如果读到数据,返回读取的长度,如果数据没有了,会返回-1
//1获取FileInputStream对象
try (FileInputStream fis = new FileInputStream("a.txt")) {

    byte[] bytes = new byte[10];
    int len;
    //读取内容到数组,如果读到数据,返回读取的长度,如果数据没有了,会返回-1
    //循环读取文件 当len为-1时停止循环
    while ((len = fis.read(bytes)) != -1) {
        //每次读取的内容转为字符串
        //newString里的参1 表示要被转换的字节数组 参2表示从头偏移多少个,参3表示 取几个数据转为字符串
        System.out.println(new String(bt, 0, len));
    }

    //fis.close();
} catch (IOException e) {
    System.out.println(e.getMessage());
}

3.4. 读取中文

  • 一个中文占3个字节

1659009203361

3.5. 文件复制案例

3.5.1. 一次读一个字节

  • 注意循环条件里的括号 容易写错
    //复制一个文件
    // 用输入流 读取文件b的内容 然后用输出流把读取的内容 写入到一个新的文件
    public static void main(String[] args) throws IOException {
        //1创建输入流对象  读取
        FileInputStream fis = new FileInputStream("module01/aaa/b.txt");
        //2创建输出流对象  写入
        FileOutputStream fos = new FileOutputStream("module01/aaa/b附件.txt");

        int by;
        //一个字节一个字节的循环读取,如果读取到-1说明读完了 结束循环
        while ((by = fis.read()) != -1) {
            System.out.println(by);
            //把读取的字节by写入到新的文件
            fos.write(by);
        }

        //3关闭流
        fis.close();
        fos.close();
    }

3.5.2. 一次读多个字节

  • 注意写入数据时,写入的是len的长度,fos.write(bt, 0, len);
    public static void main(String[] args) {
        File file1 = new File("mymodule/无间道.mp3");
        File file2 = new File("mymodule/无间道备份.mp3");

        copy(file1, file2);

    }

    /**
     * 把file1拷贝到file2
     *
     * @param file1
     * @param file2
     */
    public static void copy(File file1, File file2) {
        try (
                //创建文件的读取流
                FileInputStream fis = new FileInputStream(file1);
                //创建文件的写入流
                FileOutputStream fos = new FileOutputStream(file2);
        ) {

            //创建一个byte数组
            byte[] bts = new byte[1024];
            //定义一个变量len表示读取的长度
            int len = 0;
            //循环读取数据到数组bts中
            while ((len = fis.read(bts)) != -1) {
                //把每次读取的内容 写入到新的文件
                fos.write(bts, 0, len);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

4. 字符流介绍

4.1. 介绍

  • 由于字节流操作中文不是特别的方便,所以Java就提供字符流
    • 字符流 = 字节流 + 编码表
  • 中文的字节存储方式
    • 用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,如何识别是中文的呢?
    • 汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数

4.2. 编码表

  • 什么是字符集

    是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等

    计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBXXX字符集、Unicode字符集等

  • 常见的字符集

    • ASCII字符集:

      lASCII:是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)

      基本的ASCII字符集,使用7位表示一个字符,共128字符。ASCII的扩展字符集使用8位表示一个字符,共256字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等

    • GBXXX字符集:

      GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等

    • Unicode字符集:

      UTF-8编码:可以用来表示Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用 中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。它使用一至四个字节为每个字符编码

4.3. 编码规则:

  • 128个US-ASCII字符,只需一个字节编码
  • 拉丁文等字符,需要二个字节编码
  • 大部分常用字(含中文),使用三个字节编码
  • 其他极少使用的Unicode辅助字符,使用四字节编码

4.4. 字符串中的编码解码

  • 相关方法

    方法名 说明
    byte[] getBytes() 使用平台的默认字符集将该 String编码为一系列字节
    byte[] getBytes(String charsetName) 使用指定的字符集将该 String编码为一系列字节
    String(byte[] bytes) 使用平台的默认字符集解码指定的字节数组来创建字符串
    String(byte[] bytes, String charsetName) 通过指定的字符集解码指定的字节数组来创建字符串

代码

  • encode编码 decode解码
String str01 = "我爱我的祖国";
byte[] bytes = str01.getBytes("utf-8");
System.out.println(Arrays.toString(bytes));

byte[] bytes1 = str01.getBytes("gbk");
System.out.println(Arrays.toString(bytes1));

String str02 = new String(bytes,"utf-8");
System.out.println(str02);

String str03 = new String(bytes1,"gbk");
System.out.println(str03);

IDEA设置编码

1660793957415

4.5. 字符流类

  • Writer和Reader

  • 字节流 操作一切文件 操作汉字不方便

  • 字符流 操作文本文件 操作汉字时更方便

5. 字符输出流 FileWriter

  • Writer: 用于写入字符流的抽象父类
  • FileWriter: 用于写入字符流的常用子类

5.1. 构造方法

方法名 说明
FileWriter(File file) 根据给定的 File 对象构造一个 FileWriter 对象
FileWriter(File file, boolean append) 根据给定的 File 对象构造一个 FileWriter 对象
FileWriter(String fileName) 根据给定的文件名构造一个 FileWriter 对象
FileWriter(String fileName, boolean append) 根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象

5.2. 直接指定编码

在jdk11后,可以在FileWriter的构造方法中直接指定编码

public static void main(String[] args) throws IOException {
    //jdk11后 支持指定编码解码方式
    //参数2指定编码为utf-8 注意语法是Charset.forName()
    FileWriter fw = new FileWriter("qqqqqq.txt", Charset.forName("utf-8"));
    fw.write("开心的文字 拆迁啦");
    fw.close();

    //参数2 指定解码方式为utf-8
    FileReader fr = new FileReader("qqqqqq.txt", Charset.forName("utf-8"));
    char[] cs = new char[1000];
    int len = fr.read(cs);
    fr.close();
    System.out.println(new String(cs, 0, len));
}

5.3. 成员方法

方法名 说明
void write(int c) 写一个字符
void write(char[] cbuf) 写入一个字符数组
void write(char[] cbuf, int off, int len) 写入字符数组的一部分
void write(String str) 写一个字符串
void write(String str, int off, int len) 写一个字符串的一部分

5.4. 刷新和关闭的方法

方法名 说明
flush() 刷新流,之后还可以继续写数据
close() 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据

代码

//FileWriter fw = new FileWriter(new File("module01/ccc.txt"));
FileWriter fw = new FileWriter("module01/ccc.txt");

fw.write(100);
char[] cs = {'a', 'g', '啊', '吃'};
fw.write(cs);

fw.write("饭了,吃烤鸭,吃宫爆鸡丁,吃鱼香肉丝");

fw.write("\r\n"); //写入换行
fw.write("abcdefg", 1, 3);//跳过1个写入3个

fw.close();

5.5. 缓冲区

  • 如果write少量数据,没有flush也没有close,会发现没有写入文件
    • 数据会先传给编码器编码,然后存到编码器的缓冲数组中
    • 每次8kb存满,会刷新到硬盘中
    • 主动调用flush 或者 close方法也可以刷新到硬盘中
  • close方法执行后,不能继续使用流对象了

image-20221214175019801

6. 字符输入流 FileReader

  • Reader: 用于读取字符流的抽象父类
  • FileReader: 用于读取字符流的常用子类

6.1. 构造方法

方法名 说明
FileReader(File file) 在给定从中读取数据的 File 的情况下创建一个新 FileReader
FileReader(String fileName) 在给定从中读取数据的文件名的情况下创建一个新 FileReader

6.2. 成员方法

方法名 说明
int read() 一次读一个字符数据
int read(char[] cbuf) 一次读一个字符数组数据

代码

//FileReader fr = new FileReader(new File("module01/ccc.txt"));
FileReader fr = new FileReader("module01/ccc.txt");

//一次读一个字符
//int a;
//while ((a = fr.read()) != -1) {
//    System.out.println(a);
//    System.out.println((char) a);
//}
//System.out.println("\r\n".toCharArray().length);

//一次读多个字符到数组
char[] cs = new char[3];
int len;
while ((len = fr.read(cs)) != -1) {
    //System.out.println(cs);
    System.out.println(new String(cs, 0, len));//转为字符串 注意要添加len长度,最后一次遍历数组可能装不满
}
fr.close();

6.3. 字符流用户注册案例

  • 案例需求

    将键盘录入的用户名和密码保存到本地实现永久化存储

  • 实现步骤

    • 1获取用户输入的用户名和密码
    • 2将用户输入的用户名和密码写入到本地文件中
    • 3关流,释放资源

代码

//- 1获取用户输入的用户名和密码
//- 2将用户输入的用户名和密码写入到本地文件中
//- 3关流,释放资源
Scanner sc = new Scanner(System.in);
System.out.println("输入的用户名:");
String name = sc.next();
System.out.println("输入的密码:");
String pwd = sc.next();

try(FileWriter fw = new FileWriter("user.txt");) {
    //写入用户名
    fw.write(name);
    //换行
    fw.write("\r\n");
    //写入密码
    fw.write(pwd);
} catch (IOException e) {
    e.printStackTrace();
} 

6.4. 小结

FileOutputStream
FileInputStream  字节流  配合数组 高效进行读写 

FileWrite
FileReader  
字符流  注意里面有缓冲数组 一定要close 或者flush才能把数据写完整

7. 字符缓冲流

  • 介绍

    • BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
    • BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途
  • 构造方法

    方法名 说明
    BufferedWriter(Writer out) 创建字符缓冲输出流对象
    BufferedReader(Reader in) 创建字符缓冲输入流对象

7.1. 使用

  • BufferedWriter写数据
  • BufferedReader读数据
try (BufferedReader br = new BufferedReader(new FileReader("mymodule/d.txt"));
     BufferedWriter bw = new BufferedWriter(new FileWriter("mymodule/d1.txt"));) {

    //定义字符数组
    char[] cs = new char[1024];
    //定义长度
    int len = 0;
    while ((len = br.read(cs)) != -1) {
        bw.write(cs, 0, len);
    }

} catch (IOException e) {
    e.printStackTrace();
}

7.2. 字符缓冲流特有功能

  • 方法介绍

    BufferedWriter:

    方法名 说明
    void newLine() 写一行行分隔符,行分隔符字符串由系统属性定义

    BufferedReader:

    方法名 说明
    String readLine() 读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符如果流的结尾已经到达,则为null

代码

  • 1循环写入10次hello world到文件,每次都换行
  • 2按行读取文件内容
//    - 1循环写入10次hello world到文件,每次都换行
//创建缓冲字符写入流
try(bw = new BufferedWriter(new FileWriter("module01/xxx.txt"));){
    for (int i = 0; i < 10; i++) {
        bw.write("hello world");
        //换行
        bw.newLine();
    }

} catch (IOException e) {
    e.printStackTrace();
} 

// - 2按行读取文件内容

try( //创建缓冲字符读取流
    BufferedReader br = new BufferedReader(new FileReader("module01/xxx.txt"));) {

    //定义字符串接收读取的内容
    String line;
    //每次读取一行  内容全部读完后,如果再读 就会返回null 把循环结束
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
} 

// - 2按行读取文件内容 不打印空行  如果空行不想打印  就加一个长度的大于0的判断

BufferedReader br = new BufferedReader(new FileReader("mymodule/1.txt"));

// 一次读一行 如果没有数据 返回null
String line;
while ((line = br.readLine()) != null) {
    //空行不打印 添加一个长度的判断
    if (line.length() > 0) {
        System.out.println(line);
    }
}
br.close();
FileOutputStream      FileInputStream

FileWriter       FileReader

BufferedWriter(newLine)  BufferedReader(readLine)

7.3. 字符缓冲流操作文件中数据排序案例

7.3.1. 需求

  • 使用字符缓冲流读取文件中的数据,排序后再次写到本地文件
  • 文件内容是 5 32 47 7 5 22 2 12 4 8 6

7.3.2. 步骤

  • 1将文件中的数据读取到程序中
  • 2对读取到的数据进行分割转换排序
  • 3将排序后的数据写入到文件中

7.3.3. 代码

import java.io.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.TreeSet;
import java.util.stream.Collectors;

public class Demo01 {
    public static void main(String[] args) {
        BufferedWriter bw = null;

        try (//1 把文件数组读取  按行读一行就可以
             BufferedReader br = new BufferedReader(
                     new FileReader("mm01/aaa.txt"))
        ) {
            String line = br.readLine();
            //2 对内容分割
            String[] ss = line.split(" ");
            System.out.println(Arrays.toString(ss));

            //3 转为数字集合
            List<Integer> list01 = Arrays.stream(ss).map(Integer::parseInt).
                    collect(Collectors.toList());
            //排序
            Collections.sort(list01);
            System.out.println(list01);
            //4 对数组排序
            bw = new BufferedWriter(
                    new FileWriter("mm01/aaa.txt"));

            for (Integer num : list01) {
                bw.write(num + " ");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    System.out.println(e);
                }
            }
        }
    }
}

8. 转换流

8.1. 引言

什么是转换流?

所谓“转换流”,就是将字符流转换为字符流,它是连接字节流和字符流的桥梁。在java中,转换流有下面的两个

  • InputStreamReader
  • OutputStreamWriter

转换流是用来干什么的?

​ 我们在上文讲过了,JDK11以后,我们可以在使用字符流的时候,直接通过字符流的构造方法指定字符编码。

​ 那么在JDK11之前是怎么做的呢?这就需要使用转换流用来指定编码类型

8.2. 构造方法

方法名 说明
InputStreamReader(InputStream in) 使用默认字符编码创建InputStreamReader对象
InputStreamReader(InputStream in,String charset) 使用指定的字符编码创建InputStreamReader对象
OutputStreamWriter(OutputStream out) 使用默认字符编码创建OutputStreamWriter对象
OutputStreamWriter(OutputStream out,String charset) 使用指定的字符编码创建OutputStreamWriter对象

代码

  • 用OutputStreamWriter 写入汉字 指定编码
  • 用InputStreamReader 读取汉字 指定编码
public class ConversionStreamDemo {
    public static void main(String[] args) throws IOException {

        //创建写入的转换流对象 使用默认编码
        //OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("myCharStream\\osw.txt"));
        //创建写入的转换流对象  指定编码为gbk
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("myCharStream\\osw.txt"),"GBK");
        osw.write("中国");
        osw.close();

        //创建读取的转换流对象 使用默认编码
        //InputStreamReader isr = new InputStreamReader(new FileInputStream("myCharStream\\osw.txt"));
        //创建读取的转换流对象  指定编码为gbk
        InputStreamReader isr = new InputStreamReader(new FileInputStream("myCharStream\\osw.txt"),"GBK");
        //一次读取一个字符数据
        int ch;
        while ((ch=isr.read())!=-1) {
            System.out.print((char)ch);
        }
        isr.close();
    }
}

注意:jdk11后用字符文件读写流就可以指定编码了

    public static void main(String[] args) throws IOException {
        //jdk11后 支持指定编码解码方式
        //参数2指定编码为utf-8 注意语法是Charset.forName()
        FileWriter fw = new FileWriter("qqqqqq.txt", Charset.forName("utf-8"));
        fw.write("开心的文字 拆迁啦");
        fw.close();

        //参数2 指定解码方式为utf-8
        FileReader fr = new FileReader("qqqqqq.txt", Charset.forName("utf-8"));
        char[] cs = new char[1000];
        //读取数据到数组
        int len = fr.read(cs);
        //关闭流
        fr.close();
        System.out.println(new String(cs, 0, len));
    }

1659135641787

  • 重点: 文件拷贝要熟练

9. 对象操作流

9.1. 引言

什么是对象操作流?

对象操作流是用来将对象进行序列化操作的流。读出就是序列化,写入就是反序列化。

换句话说,就是将一个对象转换为一个流,在需要的时候,可以把这个流再转换为对象。

在java中,对象操作流有以下两个:

  • ObjectOutputStream
  • ObjectInputStream

Serializable:可序列化的 形容词

Serializer: 序列化器 名词

Serialize : 序列化 动词

9.2. 对象序列化流

  • 对象序列化介绍

    • 对象序列化:把对象转换为可以存储或传输的形式(也就是字节序列)的过程
    • 这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息
    • 字节序列写到文件之后,相当于文件中持久保存了一个对象的信息。反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
  • 对象序列化流: ObjectOutputStream

    • 将Java对象的原始数据类型和图形写入OutputStream。 可以使用ObjectInputStream读取(重构)对象。 可以通过使用流的文件来实现对象的持久存储。 如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象
  • 构造方法

    方法名 说明
    ObjectOutputStream(OutputStream out) 创建一个写入指定的OutputStream的ObjectOutputStream
  • 序列化对象的方法

    方法名 说明
    void writeObject(Object obj) 将指定的对象写入ObjectOutputStream

代码

  • 动物类
public class Anima implements Serializable {

    public String name;
    protected int age;

    public Anima() {
    }

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

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

测试类

public class Demo01 {
    public static void main(String[] args) throws IOException {
        //创建对象输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("animal.txt"));
        //Anima类要实现Serializable接口
        Anima a = new Anima("大象", 3);
        //把对象写入
        oos.writeObject(a);
        //关闭流
        oos.close();
    }
}

注意事项

  • 一个对象要想被序列化,该对象所属的类必须必须实现Serializable 接口
  • Serializable是一个标记接口,实现该接口,不需要重写任何方法

9.3. 对象反序列化流

  • 对象反序列化流: ObjectInputStream

    • ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象
  • 构造方法

    方法名 说明
    ObjectInputStream(InputStream in) 创建从指定的InputStream读取的ObjectInputStream
  • 反序列化对象的方法

    方法名 说明
    Object readObject() 从ObjectInputStream读取一个对象

代码

//创建对象输出流
ObjectInputStream oos = new ObjectInputStream(new FileInputStream("animal.txt"));

//读取出对象 然后强转为Anima对象  这个过程也叫反序列化
Anima a = (Anima) oos.readObject();
oos.close();
//打印读取的对象
System.out.println(a);

9.4. serialVersionUID

我们思考一个问题,我们用对象序列化流将一个对象序列化后,假如我们修改了对象所属的类文件,那在反序列化会不会出问题呢?

从结果来说,只要我们修改了类文件,再去反序列化的时候,java就会抛出InvalidClassException异常

Exception in thread "main" java.io.InvalidClassException: link.xiaomo.test3.Anima; local class incompatible: stream classdesc serialVersionUID = 4726774650000051461, local class serialVersionUID = -3497608134273938572

那么为什么会出现这个问题呢?

序列化运行时,将一个版本号(称为serialVersionUID)与每个可序列化类相关联,该版本号在反序列化期间用于验证序列化对象的发送方和接收方是否为该对象加载了与序列化兼容的类

如果接收方为对象加载的类与相应发送方类的serialVersionUID不同,则反序列化将导致上述异常。

那么我们要怎么解决呢

我们可以在需要序列化的类里,显示声明serialVersionUID (即自己去定义这个成员变量)。这个字段必须是staticfinal的且类型为long

  • private static final long serialVersionUID = 42L;

后面的具体数值可以自由定义,这个数字只是代表了当前类的版本号。

9.5. transient

  • 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢
    • 给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程

代码

  • 学生类
public class Anima implements Serializable {

    public static final long serialVersionUID = 42L;

    public String name;
    public int age;

    //transient表示在序列化的时候,忽略当前的字段
    public transient int weight;

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

    public Anima() {
    }

    @Override
    public String toString() {
        return "Anima{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", weight=" + weight +
                '}';
    }
  • 序列化测试
    • Anima对象 有一个400斤的体重,然后执行writeObject保存
        //创建对象输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("xiongda.txt"));

        //Anima类要实现Serializable接口
        Anima a = new Anima("熊大", 7, 400);
        //把对象写入
        oos.writeObject(a);

        oos.close();
  • 反序列化测试
    • 读取对象后打印,发现体重为0.0
    • 应为weight被transient修饰,数据没有被保存
        //创建对象输出流
        ObjectInputStream oos = new ObjectInputStream(new FileInputStream("xiongda.txt"));

        //读取出对象 然后强转为Anima对象  这个过程也叫反序列化
        Anima a = (Anima) oos.readObject();

        oos.close();
        //打印读取的对象
        System.out.println(a);//Anima{name='熊大', age=7, weight=0}

9.6. 对象操作流案例

案例需求

  • 创建多个学生类对象写到文件中,再次读取到内存中

步骤

  • 1创建序列化流对象
  • 2创建多个学生对象
  • 3将学生对象添加到集合中
  • 4将集合对象序列化到文件中
  • 5创建反序列化流对象
  • 6将文件中的对象数据,读取到内存中

代码实现

  • 学生类
public class Student implements Serializable{

    private static final long serialVersionUID = 2L;

    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.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;
    }
}
  • 测试类
public class Demo01 {
    public static void main(String[] args) {
        //创建多个学生类对象写到文件中,再次读取到内存中
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            //- 1创建序列化流对象
            oos = new ObjectOutputStream(new FileOutputStream("module01/stu.data"));
            //- 2创建多个学生对象
            Student stu01 = new Student("xiaoming", 20);
            Student stu02 = new Student("xiaohong", 21);
            Student stu03 = new Student("pgo", 30);
            //- 3将学生对象添加到集合中
            ArrayList<Student> stus = new ArrayList<>();
            stus.add(stu01);
            stus.add(stu02);
            stus.add(stu03);

            //- 4将集合对象序列化到文件中
            oos.writeObject(stus);
            //- 5创建反序列化流对象
            ois = new ObjectInputStream(new FileInputStream("module01/stu.data"));
            //- 6将文件中的对象数据,读取到内存中
            ArrayList<Student> ss = (ArrayList<Student>) ois.readObject();
            System.out.println(ss);

        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("读写失败");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            System.out.println("找不到class");
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
如人饮水,冷暖自知。
最后更新于 2023-08-05