news 2026/6/11 18:00:10

Java IO流总结

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java IO流总结

Java IO流总结

作者:没有四次元口袋的蓝胖
日期:2026-06-11
标签:Java, IO流, 字节流, 字符流, 序列化

一、IO流体系全景

IO流是Java处理数据输入输出的核心机制。"流"就是数据的管道——数据从源到目的地的流动通道。

1.1 分类维度

IO流分类 ├── 按流向 │ ├── 输入流(InputStream / Reader):读数据,从源到程序 │ └── 输出流(OutputStream / Writer):写数据,从程序到目的地 ├── 按数据单位 │ ├── 字节流(InputStream / OutputStream):操作字节,处理任意数据 │ └── 字符流(Reader / Writer):操作字符,处理文本数据 └── 按功能 ├── 节点流:直接连接数据源(文件、数组、管道等) └── 处理流(包装流):包装节点流,增强功能(缓冲、转换、对象序列化等)

1.2 完整类图

字节流 字符流 ┌──────────────┐ ┌──────────────┐ │ InputStream │ │ Reader │ └──────┬───────┘ └──────┬───────┘ │ │ ┌────────────┼────────────┐ ┌────────────┼────────────┐ │ │ │ │ FileInputStream BufferedInputStream FileReader BufferedReader ByteArrayInputStream ObjectInputStream CharArrayReader InputStreamReader FilterInputStream DataInputStream StringReader PipedReader PipedInputStream SequenceInputStream BufferedOutputStream ┌───────────────┐ ┌───────────────┐ │ OutputStream │ │ Writer │ └───────┬───────┘ └───────┬───────┘ │ │ ┌────────────┼────────────┐ ┌────────────┼────────────┐ │ │ │ │ FileOutputStream BufferedOutputStream FileWriter BufferedWriter ByteArrayOutputStream ObjectOutputStream CharArrayWriter OutputStreamWriter FilterOutputStream DataOutputStream StringWriter PipedWriter PipedOutputStream PrintStream PrintWriter

记忆口诀:字节流顶爹InputStream/OutputStream,字符流顶爹Reader/Writer。


二、字节流

2.1 InputStream核心方法

方法说明
int read()读一个字节,返回0-255,到末尾返回-1
int read(byte[] b)读最多b.length个字节到数组,返回实际读取数
int read(byte[] b, int off, int len)读len个字节到数组off偏移处
void close()关闭流,释放资源
int available()估计还能读多少字节

2.2 OutputStream核心方法

方法说明
void write(int b)写一个字节
void write(byte[] b)写整个字节数组
void write(byte[] b, int off, int len)写数组中off开始的len个字节
void flush()刷新缓冲区,强制写出
void close()关闭流(会先flush)

2.3 FileInputStream / FileOutputStream——文件字节流

最基本的文件读写,直接操作磁盘文件。

// 文件复制(经典代码)try(FileInputStreamfis=newFileInputStream("source.txt");FileOutputStreamfos=newFileOutputStream("target.txt")){byte[]buffer=newbyte[1024];// 1KB缓冲区intlen;while((len=fis.read(buffer))!=-1){fos.write(buffer,0,len);// 注意:写0到len,不是整个buffer}}// try-with-resources自动关闭

坑点1:write(byte[]) vs write(byte[], off, len)

byte[]buf=newbyte[1024];intlen=fis.read(buf);// ❌ 错误:最后一次可能只读了300字节,write(buf)会把1024字节全写出去fos.write(buf);// ✅ 正确:只写实际读取的长度fos.write(buf,0,len);

坑点2:FileOutputStream的追加模式

// 默认:覆盖写,文件内容清空重写newFileOutputStream("a.txt");// 追加写:在文件末尾继续写newFileOutputStream("a.txt",true);// 第二个参数true表示追加

2.4 BufferedInputStream / BufferedOutputStream——缓冲字节流

为什么需要缓冲?每次read/write都触发系统调用,频繁磁盘IO极慢。缓冲流在内存中维护一个缓冲区(默认8192字节),减少实际IO次数。

// 不用缓冲:每读一个字节就一次磁盘IOFileInputStreamfis=newFileInputStream("big.txt");intb;while((b=fis.read())!=-1){...}// 1MB文件 → 约100万次磁盘IO// 用缓冲:先读8KB到内存,从内存取BufferedInputStreambis=newBufferedInputStream(newFileInputStream("big.txt"));while((b=bis.read())!=-1){...}// 1MB文件 → 约128次磁盘IO

性能差距:缓冲流可以快10-100倍。

// 包装用法try(BufferedInputStreambis=newBufferedInputStream(newFileInputStream("src.txt"));BufferedOutputStreambos=newBufferedOutputStream(newFileOutputStream("dst.txt"))){byte[]buf=newbyte[1024];intlen;while((len=bis.read(buf))!=-1){bos.write(buf,0,len);}bos.flush();// 缓冲输出流写完后建议flush}

面试题:“BufferedOutputStream为什么要flush?”
→ 写入的数据先存在缓冲区,缓冲区满了才真正写磁盘。如果数据量不满一个缓冲区,close()前需要flush确保数据写出。try-with-resources的close()内部会调flush,所以一般不需要手动flush,但显式flush是好习惯。


三、字符流

3.1 为什么需要字符流

字节流读中文会出问题——UTF-8中一个汉字3字节,如果用字节流一次读1字节或读一半,就会乱码。

// ❌ 字节流读中文——乱码风险FileInputStreamfis=newFileInputStream("chinese.txt");intb;while((b=fis.read())!=-1){System.out.print((char)b);// 中文被拆散,乱码!}// ✅ 字符流读中文——按字符读,不会乱码FileReaderfr=newFileReader("chinese.txt");intch;while((ch=fr.read())!=-1){System.out.print((char)ch);// 一次读一个完整字符}

字符流 = 字节流 + 编码表。底层还是字节流,但自动处理了编码解码。

3.2 Reader / Writer核心方法

// Readerintread()// 读一个字符,返回字符编码,-1表示结束intread(char[]cbuf)// 读到字符数组intread(char[]cbuf,intoff,intlen)// Writervoidwrite(intc)// 写一个字符voidwrite(char[]cbuf)// 写字符数组voidwrite(Stringstr)// 写字符串(字符流特有,很方便)voidwrite(Stringstr,intoff,intlen)voidflush()

3.3 FileReader / FileWriter

// 文件字符复制try(FileReaderfr=newFileReader("source.txt");FileWriterfw=newFileWriter("target.txt")){char[]buf=newchar[1024];intlen;while((len=fr.read(buf))!=-1){fw.write(buf,0,len);}}

注意:FileReader/FileWriter使用系统默认编码,不能指定编码。需要指定编码时用InputStreamReader/OutputStreamWriter。

3.4 BufferedReader / BufferedWriter

和字节缓冲流同理,增加缓冲区提升性能。额外提供按行读写能力:

// BufferedReader按行读try(BufferedReaderbr=newBufferedReader(newFileReader("data.txt"))){Stringline;while((line=br.readLine())!=null){// readLine()不包含换行符System.out.println(line);}}// BufferedWriter按行写try(BufferedWriterbw=newBufferedWriter(newFileWriter("data.txt"))){bw.write("第一行");bw.newLine();// 写入系统相关的换行符(跨平台安全)bw.write("第二行");}

面试题:“readLine()读到的内容包含换行符吗?”→ 不包含。所以如果需要换行,要自己加newLine()或写\n

3.5 InputStreamReader / OutputStreamWriter——转换流

转换流是字节流和字符流的桥梁,可以指定编码:

// 指定编码读取文件try(BufferedReaderbr=newBufferedReader(newInputStreamReader(newFileInputStream("utf8.txt"),"UTF-8"))){Stringline;while((line=br.readLine())!=null){System.out.println(line);}}// 指定编码写入文件try(BufferedWriterbw=newBufferedWriter(newOutputStreamWriter(newFileOutputStream("gbk.txt"),"GBK"))){bw.write("中文内容");}

什么时候必须用转换流?

  1. 指定编码读写文件(FileReader不能指定编码)
  2. 字节流想变字符流时(如Socket的InputStream需要按字符读)

四、打印流

4.1 PrintStream / PrintWriter

打印流只有输出,没有输入。特点:永远不会抛IOException,自动flush。

// PrintStream——System.out就是PrintStreamSystem.out.println("hello");// 等价于 PrintStream ps = System.out;// PrintWriter——更通用try(PrintWriterpw=newPrintWriter("output.txt")){pw.println("第一行");pw.printf("格式化:%d, %s",100,"hello");}// PrintWriter包装OutputStreamtry(PrintWriterpw=newPrintWriter(newBufferedOutputStream(newFileOutputStream("log.txt")))){pw.println("日志内容");}// close时自动flush

面试题:“PrintStream和PrintWriter的区别?”

  • PrintStream继承OutputStream,是字节流;PrintWriter继承Writer,是字符流
  • PrintStream只能输出字节;PrintWriter可以输出字符,支持自动刷新
  • 实际开发优先用PrintWriter

五、对象流与序列化

5.1 序列化与反序列化

序列化:把Java对象转成字节序列(ObjectOutputStream)
反序列化:把字节序列恢复成Java对象(ObjectInputStream)

// 序列化:对象 → 文件publicclassUserimplementsSerializable{privatestaticfinallongserialVersionUID=1L;// 重要!privateStringname;privatetransientStringpassword;// transient:不参与序列化// constructor, getters, setters}// 写对象try(ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream("user.dat"))){oos.writeObject(newUser("张三","123456"));}// 读对象try(ObjectInputStreamois=newObjectInputStream(newFileInputStream("user.dat"))){Useruser=(User)ois.readObject();// 强制类型转换System.out.println(user.getName());// "张三"System.out.println(user.getPassword());// null!transient字段不序列化}

5.2 serialVersionUID——面试必考

// 如果不指定,JVM会自动生成一个serialVersionUID// 问题:改了类(加字段/改方法),自动生成的UID会变化// → 反序列化时UID不匹配 → InvalidClassException!// 解决:显式指定privatestaticfinallongserialVersionUID=1L;// 这样即使类有变动,只要UID一致,仍能反序列化// 新增的字段会设为默认值,删除的字段会被忽略

面试题:“serialVersionUID有什么用?不写会怎样?”
→ 用于验证序列化和反序列化的类是否兼容。不写则JVM自动生成,类一改就会UID变化,导致反序列化失败。强烈建议显式指定。

5.3 transient关键字

privatetransientStringpassword;// 序列化时跳过此字段// 反序列化后该字段为默认值(引用类型=null,基本类型=0/false)

使用场景:密码、密钥等敏感信息;不需要持久化的缓存字段。


六、常用流选择指南

场景推荐流
复制文件(任意类型)BufferedInputStream + BufferedOutputStream
读写文本文件BufferedReader + BufferedWriter
读写指定编码文本InputStreamReader/OutputStreamWriter + 编码参数
读写对象(序列化)ObjectOutputStream / ObjectInputStream
输出日志/格式化打印PrintWriter
读取键盘输入BufferedReader包装InputStreamReader
// 经典:读取键盘输入BufferedReaderbr=newBufferedReader(newInputStreamReader(System.in));Stringline=br.readLine();// Scanner更简单(但面试更常考BufferedReader方式)Scannersc=newScanner(System.in);

七、try-with-resources

7.1 传统方式 vs 新方式

// ❌ 传统方式:繁琐,容易漏关FileInputStreamfis=null;try{fis=newFileInputStream("a.txt");// ...}catch(IOExceptione){e.printStackTrace();}finally{if(fis!=null){try{fis.close();// close也可能抛异常}catch(IOExceptione){e.printStackTrace();}}}// ✅ try-with-resources:自动关闭,代码简洁try(FileInputStreamfis=newFileInputStream("a.txt");FileOutputStreamfos=newFileOutputStream("b.txt")){// ...}// 自动调用fis.close()和fos.close(),后声明的先关

7.2 原理

try-with-resources要求流实现AutoCloseable接口(或其父接口Closeable)。编译后等价于在finally中调用close(),但代码更简洁。

面试题:“多个流的关闭顺序?”
→ try-with-resources中后声明的先关闭(类似栈)。手动关时也应该先关外层(包装流),再关内层(节点流),但包装流的close()通常会自动关闭内层流。


八、面试高频题

Q1:字节流和字符流的区别?

对比项字节流字符流
操作单位字节(byte,1字节)字符(char,2字节)
基类InputStream/OutputStreamReader/Writer
适用数据任意数据(文本、图片、视频等)仅文本数据
编码处理不处理编码内置编解码
中文支持可能乱码天然支持

结论:纯文本用字符流,二进制文件用字节流。不确定就用字节流(万能)。

Q2:节点流和处理流的区别?

  • 节点流:直接连接数据源,如FileInputStream、FileReader
  • 处理流:包装节点流,增强功能,如BufferedInputStream、InputStreamReader
  • 设计模式:装饰器模式——处理流"装饰"节点流,层层增强
// 三层包装:节点流 → 缓冲流 → 按行读BufferedReaderbr=newBufferedReader(newInputStreamReader(newFileInputStream("data.txt"),"UTF-8"));

Q3:为什么Buffered流快?

减少系统调用次数。底层维护8KB缓冲区,一次IO读写8KB,内存中操作极快。不用缓冲则每读/写一个字节就一次系统调用。

Q4:IO流用了什么设计模式?

  • 装饰器模式:BufferedInputStream包装FileInputStream,动态增加缓冲功能,而不改变接口
  • 对比继承:如果用继承,每种功能组合都需要一个子类(FileBufferedInputStream、FileBufferedDataInputStream…),类爆炸
  • 装饰器模式:自由组合,需要什么功能就套什么包装

思维导图速览

Java IO流 ├── 分类 │ ├── 字节流(InputStream/OutputStream)→ 操作任意数据 │ ├── 字符流(Reader/Writer)→ 操作文本 │ ├── 输入流 vs 输出流 │ └── 节点流 vs 处理流(装饰器模式) ├── 字节流核心 │ ├── FileInputStream/FileOutputStream → 文件读写 │ ├── BufferedInputStream/BufferedOutputStream → 缓冲加速 │ ├── ObjectInputStream/ObjectOutputStream → 序列化 │ └── DataInputStream/DataOutputStream → 基本类型读写 ├── 字符流核心 │ ├── FileReader/FileWriter → 文本文件(默认编码) │ ├── BufferedReader/BufferedWriter → 缓冲 + 按行读写 │ └── InputStreamReader/OutputStreamWriter → 转换流(指定编码) ├── 特殊流 │ ├── PrintStream(System.out)→ 字节打印 │ ├── PrintWriter → 字符打印 │ └── Scanner → 读取输入(简化版) ├── 序列化 │ ├── implements Serializable │ ├── serialVersionUID → 版本兼容 │ └── transient → 跳过序列化 └── 核心要点 ├── try-with-resources自动关流 ├── write(buf, 0, len)不要写write(buf) ├── 字节流万能,字符流专攻文本 └── 缓冲流提速10-100倍

写在最后

IO流的学习核心不是背类名,而是理解体系结构

  1. 先分清字节流/字符流——顶爹不同,一切方法都围绕顶爹展开
  2. 再分清节点流/处理流——节点流干活,处理流增强,层层包装就是装饰器模式
  3. 最后记住常用组合——文件复制用Buffered字节流,文本读写用BufferedReader/Writer,序列化用Object流

面试中IO流考的不深,但有几个必考点:字节流vs字符流区别、缓冲流原理、序列化和serialVersionUID、装饰器模式。这几个答好了基本过关。

IO流在实际开发中已经不太直接用了(更多用NIO或框架),但面试频率很高,属于"工作中少用但面试必考"的知识点。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/11 17:56:57

N_m3u8DL-RE流媒体下载神器:3分钟掌握专业级视频下载技巧

N_m3u8DL-RE流媒体下载神器:3分钟掌握专业级视频下载技巧 【免费下载链接】N_m3u8DL-RE Cross-Platform, modern and powerful stream downloader for MPD/M3U8/ISM. English/简体中文/繁體中文. 项目地址: https://gitcode.com/GitHub_Trending/nm3/N_m3u8DL-RE…

作者头像 李华
网站建设 2026/6/11 17:56:57

MPC8240嵌入式处理器:经典SoC架构解析与工程实践指南

1. MPC8240:一款被低估的嵌入式“瑞士军刀”在二十世纪末到二十一世纪初的嵌入式系统黄金时代,工程师们面临着一个核心矛盾:日益增长的功能需求与有限的板卡空间、功耗预算和成本控制之间的矛盾。那个时代的解决方案,往往是在一块…

作者头像 李华
网站建设 2026/6/11 17:55:53

Daruk实战案例:构建一个完整的博客系统后端终极指南

Daruk实战案例:构建一个完整的博客系统后端终极指南 【免费下载链接】daruk a node.js web framework based on typescript 项目地址: https://gitcode.com/gh_mirrors/da/daruk Daruk是一个基于TypeScript开发的Node.js Web框架,它结合了Koa2的轻…

作者头像 李华
网站建设 2026/6/11 17:53:45

如何在6秒内完成专业级音乐源分离:Demucs完全指南

如何在6秒内完成专业级音乐源分离:Demucs完全指南 【免费下载链接】demucs Code for the paper Hybrid Spectrogram and Waveform Source Separation 项目地址: https://gitcode.com/gh_mirrors/de/demucs 你是否曾梦想过将一首复杂的歌曲快速分解成独立的乐…

作者头像 李华
网站建设 2026/6/11 17:48:18

MPC8544E嵌入式处理器:网络加速、安全引擎与DDR2内存设计实战解析

1. MPC8544E:一款被低估的嵌入式网络处理“多面手”在嵌入式网络设备的设计江湖里,选型就像给一位武林高手挑选兵器,既要趁手,又要能应对各种复杂局面。十多年前,当大家还在为如何平衡性能、功耗和集成度而绞尽脑汁时&…

作者头像 李华