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

Java IO流(2)

Java IO流(2)

作者:没有四次元口袋的蓝胖
日期:2026-06-13
标签:Java, IO流, 字符流, 缓冲流, 转换流, Properties

一、字符流

1.1 为什么需要字符流

字节流操作中文会乱码——UTF-8中一个汉字占3字节,用字节流可能把一个汉字拆成两半读,解码出错。

// ❌ 字节流读中文——乱码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);// 一次读一个完整字符}

核心区别:字节流操作字节(1 byte),字符流操作字符(2 byte),字符流内置编解码。

1.2 字符流体系

Reader(读) Writer(写) │ │ ┌───────────┼───────────┐ ┌───────────┼───────────┐ │ │ │ │ │ │ FileReader BufferedReader PipedReader FileWriter BufferedWriter PipedWriter │ │ │ │ │ readLine() │ newLine() │ │ CharArrayReader CharArrayWriter StringReader StringWriter InputStreamReader ← 转换流 OutputStreamWriter ← 转换流

1.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);}}

FileWriter的追加模式:

// 覆盖写(默认)newFileWriter("a.txt");// 追加写newFileWriter("a.txt",true);// true表示追加

局限:不能指定编码!需要指定编码时用转换流。

1.4 字符流的核心方法

// Readerintread()// 读一个字符,返回字符编码,-1表示结束intread(char[]cbuf)// 读到字符数组,返回实际读取数intread(char[]cbuf,intoff,intlen)// Writervoidwrite(intc)// 写一个字符voidwrite(char[]cbuf)// 写字符数组voidwrite(Stringstr)// 写字符串(字符流特有!字节流没有这个方法)voidwrite(Stringstr,intoff,intlen)voidflush()// 刷新缓冲区

注意:write(String str)是字符流相比字节流的一大便利,直接写字符串不需要转byte[]。


二、缓冲流

2.1 为什么需要缓冲

没有缓冲:每读/写一个字符就一次系统调用,1万个字符 = 1万次磁盘IO。

有缓冲:先读到8KB缓冲区,再从缓冲区取,1万个字符 ≈ 2次磁盘IO。

无缓冲:程序 ←→ 磁盘(每次读写都IO) 有缓冲:程序 ←→ 缓冲区(内存)←→ 磁盘(批量IO)

2.2 BufferedReader / BufferedWriter

// BufferedReader——按行读取(最大卖点)try(BufferedReaderbr=newBufferedReader(newFileReader("data.txt"))){Stringline;while((line=br.readLine())!=null){System.out.println(line);}}// BufferedWriter——按行写入try(BufferedWriterbw=newBufferedWriter(newFileWriter("data.txt"))){bw.write("第一行");bw.newLine();// 写换行符(跨平台安全:Windows写\r\n,Linux写\n)bw.write("第二行");}

readLine()细节:

  • 读到一行文本,不包含换行符
  • 读到文件末尾返回null(不是-1!)
  • 循环判断条件是!= null

面试题:“readLine()和read()的返回值有什么区别?”
→ read()返回int,到末尾返回-1;readLine()返回String,到末尾返回null。这是容易踩的坑。

2.3 缓冲字节流

同理,BufferedInputStream / BufferedOutputStream也维护8KB缓冲区:

// 文件复制——缓冲字节流(通用,任何文件都行)try(BufferedInputStreambis=newBufferedInputStream(newFileInputStream("src.jpg"));BufferedOutputStreambos=newBufferedOutputStream(newFileOutputStream("dst.jpg"))){byte[]buf=newbyte[1024];intlen;while((len=bis.read(buf))!=-1){bos.write(buf,0,len);}}

2.4 缓冲流的flush

BufferedWriterbw=newBufferedWriter(newFileWriter("a.txt"));bw.write("hello");// 此时数据可能还在缓冲区,没写到文件!bw.flush();// 强制写出// 或者 close() 内部也会调flush

什么时候需要手动flush?

  • 没有关闭流但需要立即写入时(如实时日志)
  • Socket通信中,对方需要立即收到数据
  • try-with-resources会自动close→flush,一般不需要手动

三、转换流

3.1 是什么

转换流是字节流和字符流之间的桥梁,核心能力:指定编码

字节流 ──→ InputStreamReader ──→ 字符流(指定编码读取) 字符流 ──→ OutputStreamWriter ──→ 字节流(指定编码写出)

3.2 InputStreamReader

// 指定UTF-8编码读取try(BufferedReaderbr=newBufferedReader(newInputStreamReader(newFileInputStream("utf8.txt"),"UTF-8"))){Stringline;while((line=br.readLine())!=null){System.out.println(line);}}// 指定GBK编码读取try(BufferedReaderbr=newBufferedReader(newInputStreamReader(newFileInputStream("gbk.txt"),"GBK"))){// ...}

3.3 OutputStreamWriter

// 指定GBK编码写出try(BufferedWriterbw=newBufferedWriter(newOutputStreamWriter(newFileOutputStream("gbk.txt"),"GBK"))){bw.write("中文内容");// 以GBK编码写入}

3.4 转换流 vs FileReader

对比FileReaderInputStreamReader
编码系统默认,不能指定可以指定任意编码
底层本质上就是InputStreamReader的简化版完整版
灵活性

FileReader的源码:

// FileReader源码(简化)publicclassFileReaderextendsInputStreamReader{publicFileReader(StringfileName){super(newFileInputStream(fileName));// 没传编码,用默认编码}}

结论:FileReader = InputStreamReader + 默认编码。需要指定编码时必须用InputStreamReader。

3.5 编码问题排查

// 常见编码问题:文件是GBK,用UTF-8读 → 乱码// 解决:用转换流指定正确编码// 如何判断文件编码?// 1. 看文件来源(Windows默认GBK,Linux默认UTF-8)// 2. 用工具检测(如Notepad++、jchardet)// 3. 统一用UTF-8,从源头避免问题

面试题:“如何把一个GBK文件转成UTF-8文件?”

try(BufferedReaderbr=newBufferedReader(newInputStreamReader(newFileInputStream("gbk.txt"),"GBK"));BufferedWriterbw=newBufferedWriter(newOutputStreamWriter(newFileOutputStream("utf8.txt"),"UTF-8"))){Stringline;while((line=br.readLine())!=null){bw.write(line);bw.newLine();}}

四、Properties

4.1 是什么

Properties是Hashtable<Object, Object>的子类,专门用来读写配置文件(.properties文件)。

# db.properties jdbc.url=jdbc:mysql://localhost:3306/mydb jdbc.username=root jdbc.password=123456 jdbc.driver=com.mysql.cj.jdbc.Driver

4.2 基本用法

// 创建PropertiesPropertiesprop=newProperties();// 存值prop.setProperty("username","root");prop.setProperty("password","123456");// 取值Stringusername=prop.getProperty("username");// "root"Stringhost=prop.getProperty("host","localhost");// 找不到返回默认值// 遍历Set<String>keys=prop.stringPropertyNames();for(Stringkey:keys){System.out.println(key+"="+prop.getProperty(key));}

4.3 读写配置文件——Properties的核心能力

// 从文件加载Propertiesprop=newProperties();try(FileReaderfr=newFileReader("db.properties")){prop.load(fr);// 一行搞定加载}System.out.println(prop.getProperty("jdbc.url"));// 写入文件Propertiesprop=newProperties();prop.setProperty("name","张三");prop.setProperty("age","20");try(FileWriterfw=newFileWriter("config.properties")){prop.store(fw,"This is a comment");// 一行搞定存储}

load()和store()的细节:

// load()支持的格式:// 1. key=value// 2. key:value// 3. # 或 ! 开头的是注释// 4. 空行被忽略// store()输出格式:// #This is a comment// #Sat Jun 13 06:00:00 CST 2026// name=张三// age=20

4.4 Properties的坑

坑1:setProperty的key和value都是String,但父类Hashtable支持Object

Propertiesprop=newProperties();prop.setProperty("key","value");// ✅ 推荐方式// ❌ 不推荐:用父类方法放非String类型prop.put(123,"abc");// 编译通过但getProperty取不到!prop.getProperty("123");// null,因为key类型不匹配

解决:永远只用setProperty/getProperty,不用put/get。

坑2:store()会写时间戳注释

prop.store(fw,"comment");// 输出会多一行:#Sat Jun 13 06:00:00 CST 2026// 如果不想有这行,用自定义实现

4.5 Properties vs Map

对比PropertiesHashMap
继承HashtableAbstractMap
线程安全✅(Hashtable方法synchronized)
key/value类型建议只用String任意类型
配置文件读写✅ load/store
性能较差(同步锁)

面试题:“Properties为什么继承Hashtable而不是HashMap?”
→ Properties是JDK 1.0时代的类,那时候还没有HashMap,Hashtable是唯一的Map实现。历史原因,现在来看设计不太合理(应该用组合而非继承),但为了兼容性不能改。


五、面试高频题

Q1:字符流和字节流的区别?什么时候用字符流?

字节流操作字节,处理任意数据;字符流操作字符,内置编解码,只处理文本。纯文本用字符流(避免中文乱码),二进制文件用字节流。需要指定编码时用转换流(InputStreamReader/OutputStreamWriter)。

Q2:缓冲流为什么快?

内部维护8KB缓冲区,减少系统调用次数。不用缓冲每读1字节就1次IO,用缓冲则8KB才1次IO。BufferedReader还提供readLine()按行读取的便捷方法。

Q3:转换流和FileReader的区别?

FileReader是InputStreamReader的简化版,不能指定编码(用系统默认);InputStreamReader可以在构造时指定编码。需要处理非默认编码的文件时必须用转换流。

Q4:Properties为什么继承Hashtable?有什么坑?

历史原因(JDK 1.0时代没有HashMap)。坑:父类Hashtable的put方法接受Object类型,但getProperty只能取String类型的key——用put存非String后getProperty取不到。解决:只用setProperty/getProperty。

Q5:如何把GBK文件转成UTF-8?

用InputStreamReader指定GBK编码读取,用OutputStreamWriter指定UTF-8编码写出,中间用BufferedReader/BufferedWriter包装提升效率。


思维导图速览

Java字符流进阶 ├── 字符流 │ ├── FileReader/FileWriter → 默认编码 │ ├── Reader/Writer核心方法 → write(String)是特色 │ └── 字节流vs字符流 → 二进制用字节,文本用字符 ├── 缓冲流 │ ├── BufferedReader → readLine()按行读 │ ├── BufferedWriter → newLine()跨平台换行 │ └── 8KB缓冲区 → 减少IO次数10-100倍提速 ├── 转换流 │ ├── InputStreamReader → 字节→字符(指定编码读) │ ├── OutputStreamWriter → 字符→字节(指定编码写) │ └── FileReader = InputStreamReader + 默认编码 ├── Properties │ ├── 继承Hashtable → key/value建议只用String │ ├── load() / store() → 读写.properties配置文件 │ └── setProperty / getProperty → 推荐方法 └── 面试必背五题 ├── 字符流vs字节流选型 ├── 缓冲流原理 ├── 转换流vs FileReader ├── Properties继承Hashtable的坑 └── GBK转UTF-8实现
http://www.jsqmd.com/news/1006835/

相关文章:

  • 2026 佛山节能气候门窗品牌梯队盘点:新锐实力派诺森堡门窗深度解析 - 兔兔不是荼荼
  • WaveTools鸣潮工具箱:3大核心功能全面提升游戏体验的完整指南
  • 3分钟快速上手Vin象棋:免费AI象棋教练,让你棋艺飞速提升!
  • 2026年中国到津巴布韦哈拉雷物流哪家公司靠谱?TOP8物流公司对比评测 - 优质推荐榜信息
  • tomcat8
  • 第六节:数组
  • 解密OneDev:如何用一体化平台重构现代DevOps工作流
  • primary key(`id`)
  • 长沙同城首饰回收优选,五家高评分门店汇总 - 讯息早知道
  • 影刀RPA新手教程_Windows桌面应用自动化入门从记事本到企业软件的操控
  • 2026年茂名汽修盘点:电白车主必看养护对比 - 国麟测评
  • 大模型幻觉终结者?企业级Agent RAG+知识图谱混合检索架构落地实录
  • KKS-HF Patch终极指南:一键解锁Koikatsu Sunshine全部潜力
  • 如何打造终极iOS漫画阅读体验:E-Hentai Viewer完全指南 [特殊字符]
  • 2026年6月最新连云港红宝石加热管品牌实测排行:基于服务和口碑的核心对比 - 奔跑123
  • 2026年6月最新|木纹膜品牌实力对比,口碑好的品牌厂家都在这 - 商业新知
  • 东莞二手手机哪家强?2026年top5实践经验分享! - 速递信息
  • 3分钟掌握DLSS Swapper:免费游戏性能优化终极指南
  • 2026年6月最新|装饰木纹膜品牌推荐,行业实力强、口碑好的精选 - 商业新知
  • 新手避坑指南:用Dreamview调试Apollo规划模块,这几个参数改了才有效
  • UU远程云电脑助力手机畅玩 Steam 新作 SpaceCraft!
  • zig调试 vscode
  • BilibiliDown:你的B站视频下载终极解决方案
  • 泉州口碑好的汽车贴膜店推荐泉州大膜王车衣工厂店 - GrowthUME
  • 2026 最新版 RAG 四代架构完整演进拆解!小白 程序员学大模型落地必看
  • 5大核心功能深度解析:NSC_BUILDER如何成为Switch文件管理的专业工具
  • 2026南京名表回收避坑测评|本地6家正规门店实测,行业科普干货汇总 - 薛定谔的梨花猫
  • 2026 限量奢品流通行情解析,六家回收门店综合盘点 - 讯息早知道
  • 如何用Sunshine打造你的专属游戏云主机:从痛点分析到完美串流
  • i.MX23 DCP硬件加速器:嵌入式安全处理的Scatter/Gather编程实战