一、IO流整体体系
Java IO两大分支:字节流、字符流,所有流都实现AutoCloseable接口,支持try‑with‑resources自动关闭。
| 分类 | 输入(读) | 输出(写) | 处理单元 |
|---|---|---|---|
| 字节流 | InputStream | OutputStream | byte 字节 |
| 字符流 | Reader | Writer | char 字符 |
1. 字节流:万能二进制流
父类:InputStream/OutputStream常用实现:FileInputStream、FileOutputStream
✅ 适用:所有文件:图片、视频、压缩包、文档。直接操作原始二进制,不会做编码转换。
核心API
//读:返回int,-1代表读到文件末尾 int read(); //读取1个字节 int read(byte[] buffer); //批量读到字节数组,返回读到字节数 //写 void write(int b); //写单个字节 void write(byte[] b); //写整个字节数组 void write(byte[] b,int off,int len); //数组指定范围写入⚠️重点:
read()返回int而不是byte字节范围‑128~127,如果读到字节0xFF,强转byte等于‑1,会和文件结束标记‑1冲突。所以用int接收,高24位补0,用‑1代表流结束。
FileOutputStream构造第二个参数:true=追加写入;不传/false=覆盖原文件。
2. 字符流:专门处理文本
父类:Reader/Writer常用实现:FileReader、FileWriter
✅适用:纯文本文件(.txt、.java、.md) ❌禁止处理图片视频等二进制文件,会直接损坏文件。
底层本质:字符流 = 字节流 + 编码解码,把字节按照字符集转成char。
核心API
int read(); //读1个字符,返回‑1结束 int read(char[] cbuf); //批量读到字符数组 void write(int c); void write(char[] cbuf); void write(String str);⚠️字符流自带内存缓冲区! 数据先写到内存缓冲区,没有调用flush()或者close(),数据不会落到磁盘,会出现文件空白、数据丢失。
flush():强制把缓冲区数据刷到磁盘close():关闭流,关闭前自动执行flush
3. 转换流:解决编码问题
FileReader / FileWriter会直接使用系统默认编码,跨平台会乱码。想要手动指定编码,使用转换流。
InputStreamReader:字节输入流 → 字符输入流,指定编码读文件OutputStreamWriter:字节输出流 → 字符输出流,指定编码写文件
//示例:指定UTF‑8读取文本 try(InputStreamReader isr = new InputStreamReader(new FileInputStream("test.txt"), StandardCharsets.UTF_8)){ }二、缓冲流:提升读写性能
原始FileXXX流读写频繁磁盘IO,性能差。缓冲流增加内存缓冲区,减少磁盘交互,大幅提升效率,属于包装流,必须包裹原始流。
| 字节缓冲流 | 字符缓冲流 |
|---|---|
BufferedInputStream | BufferedReader |
BufferedOutputStream | BufferedWriter |
//字节缓冲流示例 try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"))){ }
BufferedReader特有方法:readLine(),直接读取一行字符串,非常适合文本处理。BufferedWriter特有方法:newLine(),写入换行符。
缓冲流底层:默认缓冲区大小8192字节,数据先填满缓冲区,再一次性操作磁盘,减少IO次数。
三、资源关闭:两种写法
流不关闭会造成文件句柄泄漏,程序长时间运行打开大量文件,服务直接崩溃。
1. 传统 try‑finally
FileInputStream fis = null; try { fis = new FileInputStream("a.txt"); //读写操作 } catch (IOException e) { e.printStackTrace(); } finally { if(fis != null){ fis.close(); } }缺陷:try块和finally块同时抛出异常,原始异常会被掩盖丢失。
2. Java7+ try‑with‑resources【推荐】
只要实现AutoCloseable接口,在try()括号中定义流对象,代码结束自动关闭资源,不需要手动finally。
try(FileInputStream fis = new FileInputStream("a.txt"); BufferedInputStream bis = new BufferedInputStream(fis)){ //读写 }catch (IOException e){ e.printStackTrace(); }✅代码简洁、自动释放、不会丢失原始异常,开发优先使用。
❌误区:return不会关闭流!return只是方法返回,不会释放文件句柄。
四、字节流 vs 字符流完整对比
| 对比维度 | 字节流 | 字符流 |
|---|---|---|
| 顶层抽象 | InputStream / OutputStream | Reader / Writer |
| 操作单元 | byte字节 | char字符 |
| 底层原理 | 直接读写二进制 | 字节流+编码集转换 |
| 自带缓冲区 | 无(缓冲流额外包装) | 自带缓冲区 |
| flush行为 | 不需要flush | 必须flush/close落盘 |
| 适用文件 | 图片、视频、压缩包,全部文件 | txt、源码等纯文本 |
| 乱码风险 | 无编码转换,不会乱码 | 编码不匹配直接乱码 |
五、总结
read()返回值是int,判断结束条件==‑1,不要强转byte/char再判断。- 字符流忘记flush/close,文件为空,数据丢失。
- FileWriter追加模式:构造传入第二个参数true,否则每次覆盖旧文件。
- FileReader/FileWriter跟随系统编码,跨平台乱码,指定编码优先用转换流。
- 不要拿字符流读取二进制文件,文件直接损坏。
- 流资源务必释放,优先
try‑with‑resources,杜绝句柄泄漏。 - 缓冲流是包装流,关闭外层缓冲流即可,内层被包装流会自动关闭。
- 文件路径:Windows路径需要转义
D:\\demo.txt,也可以写D:/demo.txt。
六、补充:File类
File类不负责文件内容读写,只用来表示文件/目录路径,完成创建、删除、重命名、判断是否存在、遍历目录等操作。 常用方法:
exists()判断文件是否存在createNewFile()创建文件mkdir()/mkdirs()创建单级/多级目录delete()删除文件listFiles()遍历目录下全部子文件
File不能读写文件内容,读写必须依靠IO流。