当前位置: 首页 > 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.gsyq.cn/news/1518638.html

相关文章:

  • 2026年茂名汽修盘点:电白车主必看养护对比 - 国麟测评
  • 如何打造终极iOS漫画阅读体验:E-Hentai Viewer完全指南 [特殊字符]
  • zig调试 vscode
  • 如何用Sunshine打造你的专属游戏云主机:从痛点分析到完美串流
  • 2026 宁波旧包不想留了,本地哪家回收靠谱?七大门店亲测 - 薛定谔的梨花猫
  • 如何快速掌握APK Installer:Windows上的安卓应用安装完整指南
  • 抖音下载器完全指南:如何高效获取无水印视频与批量管理内容
  • 改善毛孔粗大适合用什么泥膜 6款清洁泥膜真实测评 - 全网最美
  • i.MX23 BCH ECC硬件加速器:Flash布局寄存器配置与实战指南
  • 全城可上门!沈阳各区包包回收,当场鉴定当场转账 - 讯息早知道
  • 2026年6月有名的不锈钢水箱源头厂家选哪家,SW增强型地埋水箱/一体化污水提升泵站,不锈钢水箱品牌哪家权威 - 品牌推荐师
  • 【优化求解】基于matlab粒子群算法PSO动态储位与堆垛机联合优化问题【含Matlab源码 15614期】
  • 2026年众智商学院中级经济师1280元课程费用包含什么?公共课+一门专业课怎么选和报名确认 - 众智商学院职业教育
  • 长沙热门腕表回收行情,五家门店报价实测参考 - 讯息早知道
  • PhotoDemon终极指南:如何用22MB便携式免费照片编辑器实现专业级图像处理
  • 第1章:架构基础
  • pandas MultiIndex实战:百万行数据的高效分析与内存优化
  • 美丽达新材料揭秘:地坪漆外墙仿石漆防水涂料厂家优选 - 变量人生001
  • 避坑必看!2026安徽合肥市全封闭特训学校排名,专业解析青少年叛逆、沉迷游戏、不肯上学、亲子不和 - 辛云教育资讯
  • 嵌入式定时器与DAC实战:从抗噪滤波到自动波形生成
  • ncmdump终极指南:三步解锁网易云音乐NCM格式的完整解决方案
  • 2026年菏泽CPPM和SCMP课程咨询入口:众智商学院官网、400电话和冯老师 - 众智商学院职业教育
  • HCS08硬件调试模块实战:触发设置与跟踪窗口深度解析
  • 别再为文件预览头疼了!在若依SpringBoot+Vue项目中集成kkFileView的完整指南
  • 免费投票工具软件有哪些?2026年5款零收费投票小程序实测横评,防刷+无广告才是真免费 - 微信投票小程序
  • Precision与Recall实战指南:如何在业务代价中做二元决策
  • 如何在Windows 10上实现Android应用原生运行:WSA-Windows-10项目完整技术指南
  • SKkeeper深度解析:Blender形变键与修改器协同处理的技术实现
  • 飞思卡尔56F80x GPIO寄存器配置实战:从内存映射到精准控制
  • i茅台自动预约系统终极指南:如何彻底解放双手实现智能抢购