Java IO流(2)

发布时间:2026/6/13 18:25:30

Java IO流(2) Java IO流(2)作者没有四次元口袋的蓝胖日期2026-06-13标签Java, IO流, 字符流, 缓冲流, 转换流, Properties一、字符流1.1 为什么需要字符流字节流操作中文会乱码——UTF-8中一个汉字占3字节用字节流可能把一个汉字拆成两半读解码出错。// ❌ 字节流读中文——乱码FileInputStreamfisnewFileInputStream(chinese.txt);intb;while((bfis.read())!-1){System.out.print((char)b);// 中文被拆散乱码}// ✅ 字符流读中文——正常FileReaderfrnewFileReader(chinese.txt);intch;while((chfr.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(FileReaderfrnewFileReader(source.txt);FileWriterfwnewFileWriter(target.txt)){char[]bufnewchar[1024];intlen;while((lenfr.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 有缓冲程序 ←→ 缓冲区内存←→ 磁盘批量IO2.2 BufferedReader / BufferedWriter// BufferedReader——按行读取最大卖点try(BufferedReaderbrnewBufferedReader(newFileReader(data.txt))){Stringline;while((linebr.readLine())!null){System.out.println(line);}}// BufferedWriter——按行写入try(BufferedWriterbwnewBufferedWriter(newFileWriter(data.txt))){bw.write(第一行);bw.newLine();// 写换行符跨平台安全Windows写\r\nLinux写\nbw.write(第二行);}readLine()细节读到一行文本不包含换行符读到文件末尾返回null不是-1循环判断条件是! null面试题“readLine()和read()的返回值有什么区别”→ read()返回int到末尾返回-1readLine()返回String到末尾返回null。这是容易踩的坑。2.3 缓冲字节流同理BufferedInputStream / BufferedOutputStream也维护8KB缓冲区// 文件复制——缓冲字节流通用任何文件都行try(BufferedInputStreambisnewBufferedInputStream(newFileInputStream(src.jpg));BufferedOutputStreambosnewBufferedOutputStream(newFileOutputStream(dst.jpg))){byte[]bufnewbyte[1024];intlen;while((lenbis.read(buf))!-1){bos.write(buf,0,len);}}2.4 缓冲流的flushBufferedWriterbwnewBufferedWriter(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(BufferedReaderbrnewBufferedReader(newInputStreamReader(newFileInputStream(utf8.txt),UTF-8))){Stringline;while((linebr.readLine())!null){System.out.println(line);}}// 指定GBK编码读取try(BufferedReaderbrnewBufferedReader(newInputStreamReader(newFileInputStream(gbk.txt),GBK))){// ...}3.3 OutputStreamWriter// 指定GBK编码写出try(BufferedWriterbwnewBufferedWriter(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默认GBKLinux默认UTF-8// 2. 用工具检测如Notepad、jchardet// 3. 统一用UTF-8从源头避免问题面试题“如何把一个GBK文件转成UTF-8文件”try(BufferedReaderbrnewBufferedReader(newInputStreamReader(newFileInputStream(gbk.txt),GBK));BufferedWriterbwnewBufferedWriter(newOutputStreamWriter(newFileOutputStream(utf8.txt),UTF-8))){Stringline;while((linebr.readLine())!null){bw.write(line);bw.newLine();}}四、Properties4.1 是什么Properties是HashtableObject, Object的子类专门用来读写配置文件.properties文件。# db.properties jdbc.urljdbc:mysql://localhost:3306/mydb jdbc.usernameroot jdbc.password123456 jdbc.drivercom.mysql.cj.jdbc.Driver4.2 基本用法// 创建PropertiesPropertiespropnewProperties();// 存值prop.setProperty(username,root);prop.setProperty(password,123456);// 取值Stringusernameprop.getProperty(username);// rootStringhostprop.getProperty(host,localhost);// 找不到返回默认值// 遍历SetStringkeysprop.stringPropertyNames();for(Stringkey:keys){System.out.println(keyprop.getProperty(key));}4.3 读写配置文件——Properties的核心能力// 从文件加载PropertiespropnewProperties();try(FileReaderfrnewFileReader(db.properties)){prop.load(fr);// 一行搞定加载}System.out.println(prop.getProperty(jdbc.url));// 写入文件PropertiespropnewProperties();prop.setProperty(name,张三);prop.setProperty(age,20);try(FileWriterfwnewFileWriter(config.properties)){prop.store(fw,This is a comment);// 一行搞定存储}load()和store()的细节// load()支持的格式// 1. keyvalue// 2. key:value// 3. # 或 ! 开头的是注释// 4. 空行被忽略// store()输出格式// #This is a comment// #Sat Jun 13 06:00:00 CST 2026// name张三// age204.4 Properties的坑坑1setProperty的key和value都是String但父类Hashtable支持ObjectPropertiespropnewProperties();prop.setProperty(key,value);// ✅ 推荐方式// ❌ 不推荐用父类方法放非String类型prop.put(123,abc);// 编译通过但getProperty取不到prop.getProperty(123);// null因为key类型不匹配解决永远只用setProperty/getProperty不用put/get。坑2store()会写时间戳注释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时代的类那时候还没有HashMapHashtable是唯一的Map实现。历史原因现在来看设计不太合理应该用组合而非继承但为了兼容性不能改。五、面试高频题Q1字符流和字节流的区别什么时候用字符流字节流操作字节处理任意数据字符流操作字符内置编解码只处理文本。纯文本用字符流避免中文乱码二进制文件用字节流。需要指定编码时用转换流InputStreamReader/OutputStreamWriter。Q2缓冲流为什么快内部维护8KB缓冲区减少系统调用次数。不用缓冲每读1字节就1次IO用缓冲则8KB才1次IO。BufferedReader还提供readLine()按行读取的便捷方法。Q3转换流和FileReader的区别FileReader是InputStreamReader的简化版不能指定编码用系统默认InputStreamReader可以在构造时指定编码。需要处理非默认编码的文件时必须用转换流。Q4Properties为什么继承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实现

相关新闻