前言

这篇内容主要写普通io流的相关内容。

主要包括:

  • 普通字节流
  • 普通字符流
  • 转换流
  • 打印流
  • Commons-IO 工具类

这些内容虽然没有缓冲流那么“亮眼”,但它们才是整个 IO 体系中最基础、最核心的部分。

所以这篇文章,我们就把它们统一整理一下。


引入

如果说缓冲流是在普通流外面加了一层“加速器”,那么普通流就是最基础的骨架,而特殊流则是在这个骨架基础上增加了一些额外功能,比如:

  • 编码转换
  • 便捷打印
  • 工具类封装

所以这一篇的重点,就是搞清楚:

普通流负责基础读写,特殊流负责补充功能。


一、普通字节输入流 FileInputStream

1.一次性读写一个

项目中的代码如下:

InputStream input = new FileInputStream("day03-flie-io/src/xl2");
int b;
while((b = input.read()) != -1){
    System.out.print((char)b);
}

这种写法最容易理解,但是问题也很明显:

  • 性能比较差
  • 读取中文时可能乱码

因为字节流读到的只是字节,而不是字符。


一次读取多个字节

写法如下:

InputStream input = new FileInputStream("day03-flie-io/src/xl3");

byte[] bytes = new byte[3];
int a;
while((a = input.read(bytes)) != -1){
    String string = new String(bytes, 0, a);
    System.out.println(string);
}

这种方式比一个字节一个字节读取更快。
但是如果读取的是中文文本,仍然可能出现乱码问题。

原因很简单:

字节流只负责读取原始字节,不负责按照正确编码把它翻译成字符。


一次性读取全部内容

项目中的代码如下:

InputStream is = new FileInputStream("day03-flie-io/src/xl4");

byte[] bytes = is.readAllBytes();
String s = new String(bytes);
System.out.println(s);

这种方式的优点是简单,适合小文件。
但是如果文件很大,就不推荐这样做,因为会一次性占用较多内存。


FileInputStream 的特点总结

FileInputStream 的几个关键特点如下:

  • 以字节为单位读取数据
  • 适合处理任意类型文件
  • 适合图片、音频、视频、压缩包等文件
  • 读取文本时可能出现乱码
  • 数组读取比单字节读取更常用

所以它更适合处理“文件本身”,而不适合负责“文本理解”。


二、普通字节输出流 FileOutputStream

1. 基本使用

OutputStream out = new FileOutputStream("day03-file-io/src/xiaole1", true);

out.write(97);
out.write('b');
out.write("\r\n".getBytes());

byte[] bytes = "我是".getBytes();
out.write(bytes);

out.write(bytes, 0, 3);
out.close();

2. 核心特点

(1)可以写单个字节,也可以写字节数组

常见方法有:

write(int b)
write(byte[] bytes)
write(byte[] bytes, int off, int len)

(2)可以选择覆盖写或追加写
new FileOutputStream(path)        // 覆盖
new FileOutputStream(path, true)  // 追加

这一点在写日志、补充文本内容时很常用。


(3)换行需要自己处理

因为字节流操作的是字节,所以想换行时要自己写:

out.write("\r\n".getBytes());

这也是字节流处理文本时不如字符流方便的原因之一。


三、普通字符输入流 FileReader

1. 基本使用

Reader reader = new FileReader("day03-File-io\\src\\zifuxiaole1");

char[] c = new char[3];
int len;
while((len = reader.read(c)) != -1) {
    String string = new String(c, 0, len);
    System.out.print(string);
}

2. FileReader 的特点

  • 以字符为单位读取
  • 更适合读取文本文件
  • 读取中文比普通字节流更自然
  • 不适合读取图片、视频等二进制文件

所以 FileReader 的定位很明确:

专门处理文本,不处理所有文件。


四、普通字符输出流 FileWriter

1. 基本使用

Writer fw = new FileWriter("day03-File-io/src/itheima/FileWriterDemo8/xiaole-out1", true);

fw.write('a');
fw.write(98);
fw.write("Java");
fw.write("\r\n");

char[] chars = "java".toCharArray();
fw.write(chars);
fw.write(chars, 1, 2);

2. FileWriter 的特点

  • 写文本更方便
  • 可以写字符、字符串、字符数组
  • 更适合处理纯文本内容
  • 同样支持追加写入
  • 多行内容通常需要自己写换行符

FileOutputStream 相比,FileWriter 更适合文本。
BufferedWriter 相比,FileWriter 更基础。


五、普通流和缓冲流的关系

这个地方特别容易混。

其实可以这样理解:

  • FileInputStreamFileOutputStreamFileReaderFileWriter普通流
  • BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter增强流

也就是说:

普通流负责“能读能写”,缓冲流负责“读写更快、更方便”。


六、特殊流之一:转换流 InputStreamReader

1. 基本使用

InputStream inputStream = new FileInputStream("day03-File-io\\src\\zifuxiaole3");
Reader isr = new InputStreamReader(inputStream, "GBK");
BufferedReader reader = new BufferedReader(isr);

2. 为什么需要转换流

前面我们已经提到过,字节流读取文本时可能乱码。
根本原因就在于:字节流不认识编码。

而转换流的作用就是:

把字节流按照指定编码转换成字符流。

比如这句代码:

new InputStreamReader(inputStream, "GBK")

就是告诉程序:
“这个文件里的字节,请按 GBK 编码方式转换成字符。”


3. InputStreamReader 的特点

  • 本质上是桥梁流
  • 连接字节流和字符流
  • 能解决不同编码下的乱码问题
  • 在中文文本读取中非常常见

所以当我们处理:

  • UTF-8 文件
  • GBK 文件
  • 网络传输文本
  • 文件导入内容

时,转换流是很有必要的。


七、特殊流之二:打印流 PrintWriter

1. 基本使用

PrintWriter ps = new PrintWriter(
    new FileOutputStream("day03-File-io/src/itheima/bufferedandinputoutput/Demo13inputstreamreader/printtest1", true)
);

ps.println(97);
ps.println("我");
ps.println('你');
ps.println(true);

2. PrintWriter 的特点

(1)输出更方便

普通流输出内容时,经常要考虑字节、字符、编码等问题。
而打印流直接提供了:

print()
println()

所以写起来更顺手。


(2)可以直接打印多种数据类型

项目中就打印了:

  • 整数
  • 字符串
  • 字符
  • 布尔值

这说明 PrintWriter 的通用性很强。


(3)适合输出格式化文本

打印流常用于:

  • 日志输出
  • 文本结果输出
  • 报表输出
  • 控制台打印

在这类场景中,它比普通输出流更方便。


八、特殊内容补充:Commons-IO 工具类

FileUtils.copyFile(new File("day03-File-io\\src\\xl2"), new File("day03-File-io\\src\\xl11"));
FileUtils.forceDelete(new File("day03-File-io\\src\\xl11"));

这里用到了 org.apache.commons.io.FileUtils

它不是 JDK 原生流类,但它是一个很实用的 IO 工具类库。


1. 它的特点

  • 封装了很多常见文件操作
  • 代码更简洁
  • 减少了手动写流复制的重复代码

2. 常见用途

  • 复制文件
  • 删除文件
  • 复制目录
  • 读取文件
  • 写入文件

可以把它理解成:

在 IO 流基础上的一个工具箱。


九、普通流和特殊流怎么选

这里我直接做一个简单总结。

1. 处理任意文件

优先考虑:

  • FileInputStream
  • FileOutputStream
  • BufferedInputStream
  • BufferedOutputStream

2. 处理文本文件

优先考虑:

  • FileReader
  • FileWriter
  • BufferedReader
  • BufferedWriter

3. 涉及编码转换

优先考虑:

  • InputStreamReader

4. 需要方便打印

优先考虑:

  • PrintWriter

5. 想快速完成常见文件操作

可以考虑:

  • FileUtils

总结

这次把io流中除了缓冲流之外的剩余内容也梳理了一遍,整个 IO 流体系就更完整了。

可以这样理解:

  • 普通流是基础
  • 缓冲流是增强
  • 转换流是桥梁
  • 打印流是便捷输出
  • Commons-IO 是工具封装

最后再总结一下它们各自的定位:

  • FileInputStream / FileOutputStream:处理任意文件
  • FileReader / FileWriter:处理文本文件
  • BufferedXXX:提升效率、增强体验
  • InputStreamReader:解决编码转换问题
  • PrintWriter:输出更方便
  • FileUtils:简化常见文件操作

如果说缓冲流解决的是“快不快”,
那么普通流和特殊流解决的就是“能不能做、怎么做更合适”。


更多推荐