一、 核心本质:到底什么是 IO?
通俗理解:
IO(Input/Output)就是搬运数据。
-
Input(输入): 数据从外部(磁盘、网络)流入你的程序(内存)。
-
Output(输出): 数据从你的程序(内存)流向外部。
严谨定义:
在 Java 中,IO 流是指数据在两设备间的有序传输。它是单向的,就像水管里的水,要么流进,要么流出。
二、 四大祖先
Java IO 有几十个类,但它们都逃不出这四个抽象基类:
| 处理单位 | 输入流 (读) | 输出流 (写) | 角色定位 (通俗理解) | 核心场景 |
| 字节 (Byte) | InputStream | OutputStream | 底层搬运工:原封不动搬运二进制数据 | 图片、视频、压缩包、网络传输 |
| 字符 (Char) | Reader | Writer | 高级翻译官:自动处理字符编码转换 | TXT、HTML、配置文件、日志 |
三、 字节流 vs 字符流:搬运工与翻译官的较量
-
字节流(InputStream/OutputStream):
-
本质: 它是最底层的操作。计算机只认识 0 和 1(字节)。
-
适用场景: 图片、视频、音频、PDF 等。
-
严谨点: 字节流不会处理编码。如果用它读中文,一个汉字被拆成 3 个字节读取,中间一旦断开就会乱码。
-
-
字符流(Reader/Writer):
-
本质: 字节流 + 编码表 = 字符流。
-
适用场景: 纯文本(.txt, .java, .html)。
-
通俗比喻: 字符流在读取字节后,会自动查字典(UTF-8/GBK),把字节拼成汉字给你。
-
FileInputStream 代码示例:
try (InputStream fis = new FileInputStream("input.txt")) {
System.out.println("Number of remaining bytes:"
+ fis.available());
int content;
long skip = fis.skip(2);
System.out.println("The actual number of bytes skipped:" + skip);
System.out.print("The content read from file:");
while ((content = fis.read()) != -1) {
System.out.print((char) content);
}
} catch (IOException e) {
e.printStackTrace();
}
Number of remaining bytes:11
The actual number of bytes skipped:2
The content read from file:HPS666
FileOutputStream 代码示例:
try (FileOutputStream output = new FileOutputStream("output.txt")) {
byte[] array = "JavaGuide".getBytes();
output.write(array);
} catch (IOException e) {
e.printStackTrace();
}
FileReader 代码示例:
try (FileReader fileReader = new FileReader("input.txt");) {
int content;
long skip = fileReader.skip(3);
System.out.println("The actual number of bytes skipped:" + skip);
System.out.print("The content read from file:");
while ((content = fileReader.read()) != -1) {
System.out.print((char) content);
}
} catch (IOException e) {
e.printStackTrace();
}
The actual number of bytes skipped:3
The content read from file:我是HPS
FileWriter 代码示例:
try (Writer output = new FileWriter("output.txt")) {
output.write("你好,我是HPS。");
} catch (IOException e) {
e.printStackTrace();
}
代码对比:
// 字节流读文件(虽然能读,但遇到中文容易“碎”掉)
try (FileInputStream fis = new FileInputStream("test.txt")) {
int content;
while ((content = fis.read()) != -1) {
System.out.print((char) content); // 强转可能乱码
}
}
// 字符流读文件(自带翻译功能,稳!)
try (FileReader fr = new FileReader("test.txt")) {
int content;
while ((content = fr.read()) != -1) {
System.out.print((char) content);
}
}
四、 转换流:跨越鸿沟的桥梁!!!!!!
核心问题: 既然读取底层全是字节,那怎么把字节流变成字符流?
这就是 InputStreamReader 的作用。
-
InputStreamReader是字节流转换为字符流的桥梁,其子类FileReader是基于该基础上的封装,可以直接操作字符文件。 -
// 字节流转换为字符流的桥梁 public class InputStreamReader extends Reader { } // 用于读取字符文件 public class FileReader extends InputStreamReader { } -
OutputStreamWriter是字符流转换为字节流的桥梁,其子类FileWriter是基于该基础上的封装,可以直接将字符写入到文件。 -
// 字符流转换为字节流的桥梁 public class OutputStreamWriter extends Writer { } // 用于写入字符到文件 public class FileWriter extends OutputStreamWriter { }严谨表达: 这就是适配器模式的体现,它解决了“接口不兼容”的问题。
五、 缓冲流:给搬运工配一辆推车
这是提升性能的关键。
为什么要用缓冲流?
如果没有缓冲流,你每读一个字节,程序就要去翻一次硬盘。硬盘太慢了,CPU 会在旁边等得发疯。
缓冲流的做法:
在内存里开辟一个 8KB 的缓冲区(Buffer)。
-
读数据时: 一次性从硬盘填满 8KB 到内存,你调用
read()时直接从内存拿。 -
写数据时: 先写到内存,攒满 8KB 后再“哗啦”一下整块写入硬盘。
BufferedInputStream(字节缓冲输入流)
BufferedInputStream 内部维护了一个缓冲区,这个缓冲区实际就是一个字节数组,通过阅读 BufferedInputStream 源码即可得到这个结论:
public
class BufferedInputStream extends FilterInputStream {
// 内部缓冲区数组
protected volatile byte buf[];
// 缓冲区的默认大小
private static int DEFAULT_BUFFER_SIZE = 8192;
// 使用默认的缓冲区大小
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
// 自定义缓冲区大小
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
}
缓冲区的大小默认为 8192 字节,当然了,你也可以通过 BufferedInputStream(InputStream in, int size) 这个构造方法来指定缓冲区的大小。
性能飞跃代码:
// 新建一个 BufferedInputStream 对象
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("input.txt"));
// 读取文件的内容并复制到 String 对象中
String result = new String(bufferedInputStream.readAllBytes());
System.out.println(result);
BufferedOutputStream (字节缓冲输出流)
BufferedOutputStream 将数据(字节信息)写入到目的地(通常是文件)的过程中不会一个字节一个字节的写入,而是会先将要写入的字节存放在缓存区,并从内部缓冲区中单独写入字节。这样大幅减少了 IO 次数,提高了效率
字符缓冲流
BufferedReader (字符缓冲输入流)和 BufferedWriter(字符缓冲输出流)类似于 BufferedInputStream(字节缓冲输入流)和BufferedOutputStream(字节缓冲输入流),内部都维护了一个字节数组作为缓冲区。不过,前者主要是用来操作字符信息。

977

被折叠的 条评论
为什么被折叠?



