当前位置: 首页 > news >正文

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或框架),但面试频率很高,属于"工作中少用但面试必考"的知识点。

http://www.gsyq.cn/news/1505467.html

相关文章:

  • 2026年6月最新版遵义第三方CMACNAS甲醛检测治理机构口碑名单:万清CMA检测中心等5家公司深度测评万清CMA检测中心TOP1推荐 - 一修哥咨询
  • 深度解码:为什么你的PCSX2跑不满60帧?3个被忽视的性能瓶颈揭秘
  • 2026山东五恒空调厂家实力排行:核心维度实测对比 - 起跑123
  • 从LXC到Docker:深入解析容器技术的演进、核心原理与选型指南
  • 2026年6月电子线生产厂家口碑推荐,行业内电子线源头厂家,耐化学腐蚀,延长使用寿命 - 品牌推荐师
  • 超元力玻璃剧场轻量化落地体系,构筑文旅业态长效运营新基石
  • 昆明社区回收店测评:家门口小店靠谱吗?实测结果意外 - 奢侈品回收评测
  • 华硕笔记本性能调优神器:5步掌握G-Helper完整使用指南
  • 2026 韶关黄金回收价位盘点 全城实体门店综合测评 - 靖昱黄金回收
  • 从零到一:手把手教你打造STC89C52RC最小系统板
  • 国内激光清障仪主流厂家实力排行及核心资质盘点 - 奔跑123
  • 面向企业知识库问答的 RAG 落地实践:大模型如何从“会聊天”变成“懂业务”
  • 如何在10分钟内彻底掌握Etcher镜像烧录工具的核心用法
  • SD-PPP:Photoshop AI插件终极免费指南,让设计创作更智能高效
  • 2026年PCBA加工丨smt加工丨贴片加工行业十大靠谱工厂榜单出炉,广东东莞这家企业凭什么入选? - 变量人生001
  • 从零上手树莓派:系统烧录与无屏无线连接实战
  • DDrawCompat:让Windows 11流畅运行经典DirectX老游戏的兼容性解决方案
  • TripoSR高性能3D重建架构解析与生产环境部署指南
  • Layui-admin:企业级后台管理系统的极速开发解决方案
  • 三步掌握猫抓插件:小白也能轻松下载网页视频音频
  • TransitionableTab自定义动画教程:解锁4种预设效果与无限可能
  • 劳力士没有保卡还能高价回收吗?来沈阳收的顶当面检测成色细节给你答案 - 奢侈品回收评测
  • 2026济南名表回收靠谱渠道盘点无套路高价变现攻略 - 奢侈品回收评测
  • 温度采集卡怎么选?ZLinear三款主流型号深度横评
  • openEuler嵌入式开发:面向IoT和边缘计算的完整解决方案
  • 2026企业微信SCRM收费标准:全国统一报价+无隐形消费指南 - 资讯速览
  • 2026:青神县新房除甲醛公司横向测评,实地对比后优先选四川家之源环保科技有限公司 - 专注室内空气检测治理
  • Daruk实战案例:构建一个完整的博客系统后端终极指南
  • aardio - 【实战】用scottPlot图表库打造交互式数据可视化面板
  • 2026年金华电商财税公司最新名单及选择指南 - 财税合规行业评测官网