10.1 的 File 只是路标,搬数据要靠流。字节流 = 万能搬运工:硬盘和内存之间一根管道,一次搬一个字节——图片、视频、音乐、文本,只要是文件它都能搬。本节三大件:写(FileOutputStream)、读(FileInputStream)、复制(边读边写)。
1. 引入:流是什么
流(Stream)就是数据搬运的管道:数据像水一样从一头流向另一头。方向以内存为家:
- 输出流(OutputStream):内存 → 硬盘,"写出去"
- 输入流(InputStream):硬盘 → 内存,"读进来"
为什么叫"字节"流?因为硬盘上的一切本来就是字节——图片是字节、视频是字节、文本也是字节。字节流不懂内容只管搬,所以万能;想要"看懂内容",那是字符流的活儿(后面的钩子)。
2. 写数据:FileOutputStream
FileOutputStream fos = new FileOutputStream("test\\b.txt"); // ① 接管道
fos.write(97); // ② 写一个字节 → a
fos.write("波仔666".getBytes()); // 字符串要先 getBytes() 拆成字节数组(6.2 的账)
fos.write("\r\n".getBytes()); // 换行:Windows 记事本认 \r\n
fos.write("第二行".getBytes()); // 追加写
fos.close(); // ③ 关管道:释放资源 + 数据落袋
三条保命规矩(构造那一刻就决定了):
- 文件不存在 → 自动创建(注意:这和
new File不一样!流构造是干活的,真建文件)。 - 文件存在 → 默认清空重来!想接着写,构造第二参数传
true:java new FileOutputStream("test\\b.txt", true); // 追加模式 - 必须
close()——不关,数据可能滞留在缓冲区没真正落盘,资源也一直占着。
FileOutputStream 构造和读写都会抛 IOException(受检,5.2 的账)。标准姿势是 try-catch-finally,close() 放 finally 里保证必执行(5.3 的账)——JDK7 之后还有更省事的 try-with-resources:
try (FileOutputStream fos = new FileOutputStream("b.txt")) {
fos.write(97);
} catch (IOException e) {
e.printStackTrace();
} // 括号里声明的流,出括号自动 close,不用手关
3. 读数据:FileInputStream
FileInputStream fis = new FileInputStream("test\\b.txt");
int b;
while ((b = fis.read()) != -1) { // 一次读一个字节
System.out.print((char) b);
}
fis.close();
两个要点:
read()返回int,不是byte——因为"读完了"得有个信号,约定的哨兵值是 -1。可 -1 写成 8 个二进制 1,跟正常字节数据撞车;换成 4 字节的 int 装,正常数据永远是 0~255,-1 独一无二。哨兵机制,读循环的标准套路。- 一次一个字节太慢——用"小推车"批量搬:
java byte[] cart = new byte[1024]; int len; while ((len = fis.read(cart)) != -1) { // 返回本次实际搬了几个 System.out.print(new String(cart, 0, len)); // 只翻译前 len 个 }
4. 收网旧账
- 10.1 File 类:路标指哪,流就接哪——
new FileOutputStream(File对象)两种构造一家亲。 - 6.2 字符串类:
getBytes()把字符串拆成字节数组;new String(bytes, 0, len)反向拼回——一拆一装,翻译细节下节细说。 - 5.3 异常处理:
close()放 finally 的标准姿势 + try-with-resources 新姿势。 - 2.7 数组:
byte[]小推车、new String(cart, 0, len)的"含头不含尾"同款规矩。 - 9.5 的分水岭:框架读配置文件,第一根管道就是字节流——IO 是"程序接触外部世界"的开始。
5. 动手:Demo06 复制一张图片(字节流的成人礼)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo06 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("photo.jpg"); // 源
FileOutputStream fos = new FileOutputStream("photo_copy.jpg"); // 副本
byte[] cart = new byte[1024];
int len;
long count = 0;
while ((len = fis.read(cart)) != -1) { // 搬一车
fos.write(cart, 0, len); // 卸一车
count += len;
}
System.out.println("复制完成,共 " + count + " 字节");
fos.close();
fis.close(); // 后开的先关
}
}
跑完打开副本——和原图一模一样。文本、图片、视频,复制代码一模一样:这就是"字节流万能"的含义。
三个 30 秒实验:
- 连跑两次 Demo06——副本没变?因为"复制"每次都是新建 + 全量覆盖。再试试用
new FileOutputStream("b.txt")往已有文件写一句——原内容被清空了(保命规矩 2 的现场)。 - 把
fos.write(cart, 0, len)改成fos.write(cart)再复制一个文本——末尾多了一串空字符:小推车没卸干净,上次剩的旧货也被写出去了(所以必须带0, len)。 - 用
read()单字节版逐个打印 b.txt 内容——中文变乱码或缺一半:中文占多个字节,单字节打印把"半个字"当独立数据翻了。字节流管搬不管翻译——翻译是字符流的活(下节钩子)。
6. 常见误区
- ❌ 以为
new FileOutputStream没副作用 —— 文件不在会自动建;文件在会清空!要追加必须第二参数传true。 - ❌ 忘
close()—— 数据滞留缓冲区没落盘(文件空的/缺尾巴),资源也一直占着。close 是收工仪式,不是可选项。 - ❌ Windows 下用
\n换行 —— 记事本不认,要\r\n;写代码里就是"\r\n".getBytes()。 - ❌ 把
read()的返回值当 byte 收 —— 哨兵 -1 要求用 int 接;byte b永远判断不到"读完"。 - ❌ 用字节流读中文文本并直接打印 —— 中文多字节被拆散,打印乱码。字节流复制文本没事(原样搬运),但"读出来给人看"要等字符流。
7. 可折叠自测
前者真干活:文件不在自动创建,在则清空;后者只是内存里造路标,磁盘纹丝不动。追加写要给构造传第二参数 true。
需要"读完"的哨兵值 -1。byte 是 8 位,-1 的 8 个 1 和正常数据撞车;int 是 32 位,正常数据 0~255,-1 独一份。
返回本次实际读到的字节个数。cart 是复用的小推车,末尾可能残留上一次的旧数据,直接翻译全车会把旧货也翻出来——只翻前 len 个。
把流声明在 try 后的括号里,出括号自动 close——即使中途抛异常也保证关,代码少一层嵌套,不用手写 finally。
try (FileInputStream fis = new FileInputStream("src.jpg");
FileOutputStream fos = new FileOutputStream("dst.jpg")) {
byte[] cart = new byte[1024];
int len; long count = 0;
while ((len = fis.read(cart)) != -1) {
fos.write(cart, 0, len);
count += len;
}
System.out.println("共复制 " + count + " 字节");
} catch (IOException e) {
e.printStackTrace();
}
写出来,你就能用 Java 干"Ctrl+C / Ctrl+V"底层干的活了。
8. 下一步学什么
- ☑ 10.3 字符流 ✅ —— 点此复习:翻译官上岗(GBK/UTF-8)、乱码铁律、FileWriter/FileReader、转换流指定编码、flush vs close、字符流只碰文本。
相关笔记
- 第十章 I-O/10.1 File类:路标指路,流搬运——IO 章的黄金搭档
- 第五章 异常/5.3 异常处理及语法:finally 关流的标准姿势与 try-with-resources 的前身
- 第五章 异常/5.2 运行时异常与编译时异常:IOException 受检——外部世界必受检
- 第六章 Java API/6.2 字符串类:getBytes 与 new String(bytes,0,len) 的一拆一装
- 第二章 Java编程基础/2.7 数组:byte[] 小推车与"含头不含尾"的老规矩
- 第一章 Java开发入门/1.4 Java程序的运行机制:字节是数字世界的原子——流只搬原子不问含义