IO流

用于读写文件或网络中的数据, 以程序为参照

image-20250118234155055

字节流

InputStream

FileInputStream

  • 创建字节输入流对象

    • 文件不存在直接报错
  • 读数据

    image-20250118234222531

    • 一次读一个字节, 读出的数据为在ASCII上对应的数字

    • 读到文件末尾了, read方法返回-1

    • 循环读取

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // 循环读取一个字节数据
      int b;
      while((b = fis.read()) != -1) {
      sout((char) b);
      }

      // 循环读取一个容量大小的字节数据
      int len;
      byte[] bytes = new byte[1024 * 1024 * 5];
      while((len = fis.read(bytes)) != -1) {
      sout(new String(bytes, 0, len));
      }
  • 释放资源

    • 每次使用完流后都要释放资源

OutputStream

FileOutputStream

  • 创建字节输出流对象

    • 参数是字符串表示的路径或者是File对象都是可以的
    • 如果文件不存在会创建一个新的文件, 但是要保证父级路径是存在的
    • 如果文件已经存在, 则会清空文件, 可以通过第二个参数打开续写
  • 写数据

    • write方法的参数是整数, 但是实际上写到本地文件中的是对应的ASCII字符
    • 可以写出字节数组
    • 换行符 \r\n
  • 释放资源

    • 每次使用完流后都要释放资源

字符集

  • ASCII: 英文字符, 一个字节

    • 编码规则: 前面补0, 补齐8位
    • 解码规则: 直接转成十进制
  • GBK: 简体中文版Windows默认使用

    • 英文用一个字节存储, 完全兼容ASCII
      • 编码规则: 前面补0, 补齐8位
    • 汉字两个字节存储, 高位字节二进制一定以1开头, 转成十进制之后是一个负数
      • 编码规则: 不需要变动
      • 解码规则: 直接转成十进制
  • Unicode

    • UTF-8编码规则: 用1~4个字节保存

      image-20250118234243830

  • 避免乱码

    • 不要用字节流读取文本文件
    • 编码解码时使用同一个码表, 同一个编码方式

Java中编码和解码方法

  • 编码

    1
    2
    public byte[] getBytes()					// 使用默认方式编码
    public byte[] getBytes(String charsetName) // 使用指定方式编码
  • 解码

    1
    2
    String(byte[] bytes)						// 使用默认方式解码
    String(byte[] bytes, String charsetName) // 使用指定方式解码

字符流

字符流 = 字节流 + 字符集

Reader

FileReader

  • 创建字符输入流对象

    • 底层: 关联文件, 并创建缓冲区(长度为8192的字节数组)
  • 读取数据

    • 底层:

      • 判断缓冲区中是否有数据可以读取

      • 缓冲区没有数据

        • 就从文件中获取数据, 装到缓冲区中, 每次尽可能装满缓冲区
        • 如果文件中也没有数据了, 返回-1
      • 缓冲区有数据: 从缓冲区中读取

    • 按字节进行读取, 遇到中文, 一次读多个字节, 读取后解码, 返回整数

    1
    2
    3
    4
    5
    6
    7
    // 读取数据, 读到末尾返回-1
    // 一次读取一个字节, 遇到中文一次读多个字节, 把字节解码并转成十进制返回
    public int read()

    // 读取多个数据, 读到末尾返回-1, 返回值是读出的长度
    // 把读取字节, 解码, 强转三步合并, 强转之后的字符放到数组中
    public int read(char[] buffer)
  • 释放资源

Writer

FileWriter

  • 创建字符输出流对象

    • 底层: 也会创建长度为8192的缓冲区
    • 参数是字符串表示的路径或者File对象都是可以的
    • 如果文件不存在会创建一个新的文件, 但要保证父级路径是存在的
    • 如果文件已经存在, 则会清空文件, 如果不想清空可以打开续写开关
  • 写数据

    • 底层: 填满缓冲区, 手动flush, close都会写入数据
    • 如果 write 方法的参数是整数, 实际写到文件中的是对应的ASCII字符
    • 根据字符集的编码方式进行编码, 把编码之后的数据写到文件中
  • 释放资源

字节流和字符流的使用场景

  • 字节流: 拷贝任意类型的文件

  • 字符流: 读取纯文本文件中的数据, 往纯文本文件中写出数据

缓冲流

字节缓冲流

image-20250118234257194

  • 在构造方法时其实就是将基本流进行包装

    1
    2
    public BufferedInputStream(InputStream is)
    public BufferedOutputStream(OutputStream os)
  • 关流时只要关闭缓冲流

字符缓冲流

  • 由于字符流已有缓冲区, 故字符缓冲流对效率影响不大

  • 特殊方法

    1
    2
    3
    4
    5
    6
    7
    public BufferedReader(Reader r)
    // 在读取时, 一次读一整行, 遇到回车换行结束, 但不会读入回车换行, 遇到末尾返回null
    public String readLine()

    public BufferedWriter(Writer r)
    // 跨平台的换行
    public void newLine()

转换流

字符流和字节流之间的桥梁

image-20250118234320987

1
2
3
4
// 字符转换输入流
InputStreamReader
// 字符转换输出流
OutputStreamWriter
  • 当字节流想要使用字符流中的方法时可以使用

  • 指定字符集读写数据(JDK11后淘汰)

序列化流

可以把java中的对象写到本地文件中

1
2
3
4
5
// 把基本流包装成高级流
public ObjectOutputStream(OutputStream out)

// 把对象序列化(写出)到文件中去
public final void writeObject(Object obj)
  • 要给对象实现Serializable接口

    • 这个接口里没有抽象方法, 是标记型接口
    • 一旦实现该接口, 就表示当前对象可以被序列化

反序列化流

可以把序列化到本地文件中的对象读取到程序中

1
2
3
4
5
// 把基本流变成高级流
public ObjectInputStream(InputStream out)

// 可以把序列化到本地文件中的对象读取到程序中
public Object readObject()

序列化流和反序列化流细节汇总

  • 使用序列化流将对象写到文件时, 需要让javaBean类实现Serializable接口, 否则会出现NotSerializableException异常

  • 序列化流写到文件中的数据是不能修改的, 一旦修改就无法再次读回来了

  • 序列化对象后, 修改了javaBean类, 再次反序列化会出现问题, 会抛出InvalidClassException异常

    • 解决方案: 给JavaBean类添加serialVersionUID(序列号, 版本号)
  • 如果一个对象中的某个成员变量的值不想被序列化, 可以给该成员变量添transient关键字修饰, 该关键字标记的成员变量不参与序列化过程

  • 规范下将对象放入集合中读写, 前提集合实现Serializable接口

打印流

  • 只有输出流, 只能操作文件目的地, 不操作数据源

  • 特有的写出方法可以实现, 数据原样写出

  • 特有的写出方法, 可以实现自动刷新, 自动换行

    • 打印一次数据 = 写出 + 换行 + 刷新
  • PrintStream ps = System.out;

    • 获取打印流对象, 此打印流在虚拟机启动的时候, 由虚拟机创建, 默认指向控制台
    • 特殊的打印流, 系统中的标准输出流, 不能关闭, 在系统中是唯一的

字节打印流

  • 字节流底层没有缓冲区, 构造方法时开不开自动刷新都一样

  • 构造方法: public PrintStream(...)

特有方法

1
2
3
4
5
6
7
8
// 打印任意数据, 自动刷新, 自动换行
public void println(Xxx xx)

// 打印任意数据, 不换行
public void print(Xxx xx)

// 带有占位符的打印语句, 不换行
public void printf(String format, Object... args)

字符打印流

  • 字符流底层有缓冲区, 想要自动刷新需要开启

  • 构造方法: public PrintWriter(...)

特有方法

  • 字节打印流的特有方法一样

解压缩流

  • 解压的本质: 把压缩包里面的每一个文件或文件夹读取出来, 按照层级拷贝到目的地中

  • Java中压缩包中的文件(夹)实际上是一个个ZipEntry对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 创建一个解压缩流用来读取压缩包中的数据
ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
// 获取压缩包里的每一个ZipEntry对象
ZipEntry entry;
while((entry = zip.getNextEntry()) != null) {
if (entry.isDirectory()) {
File file = new File(dest, entry.toString());
file.mkdirs();
} else {
FileOutputStream fos = new FileOutputStream(new File(dest, entry.toString());
int b;
while((b = zip.read()) != -1) {
// 写到目的地
fos.write(b);
}
fos.close();
// 表示压缩包中的一个文件处理完毕了
zip.closeEntry();
}
}
zip.close();

压缩流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
* 压缩文件
*/
// 创建压缩流关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest, "a.zip")));
// 创建ZipEntry对象, 表示压缩包里面的每一个文件和文件夹
ZipEntry entry = new ZipEntry("a.txt");
// 把ZipEntry对象放到压缩包当中
zos.putNextEntry(entry);
FileInputStream fis = new FileInputStream(src);
int b;
while((b = fis.read()) != -1) {
zos.write(b);
}
zos.closeEntry();
zos.close();

常用工具类

Commons-io

Hutool

Properties配置文件

  • properties是一个双列结合, 拥有Map集合所有的特点

  • 拥有IO特性, 特有的 store 和 load 方法, 处理 .properties 文件