Java学习笔记-文件IO

八、文件 IO

基础介绍

程序运行时的数据都在内存里,程序一关数据就消失。文件 IO(Input/Output)就是把数据写到硬盘文件里持久保存,或者从硬盘文件读回内存。

两大流体系:

体系处理单位适合场景类名
字节流byte(字节)二进制数据(图片、视频、对象序列化)InputStream / OutputStream
字符流char(字符)文本数据(txt、csv、json)Reader / Writer

报价系统用字符流:报价单数据是文本,用 FileReader/FileWriter 或 BufferedReader/BufferedWriter。

带缓冲 vs 不带缓冲:

类型特点比喻
不带缓冲(FileReader/FileWriter)读一个字节写一个字节,频繁访问硬盘用勺子一勺勺舀水
带缓冲(BufferedReader/BufferedWriter)先装满缓冲区,再一次性读写用桶打一桶水再倒

always 用缓冲流,效率差几十倍。

为什么构造器一层包一层?(装饰器模式)

▼
java
复制代码
new BufferedWriter( // 第3层:加缓冲(效率) new OutputStreamWriter( // 第2层:指定编码(字符 → 字节) new FileOutputStream(file), // 第1层:连接文件(字节 → 硬盘) "UTF-8" ) )
层级类作用
最底层FileOutputStream直接连硬盘,处理原始字节
中间层OutputStreamWriter把字符按指定编码转成字节
最外层BufferedWriter加缓冲区,减少硬盘访问次数

每一层都是给上一层"加功能",像俄罗斯套娃。可以只包一层,也可以包多层。


核心方法表

File 类(文件/目录操作)

方法作用
File(String path)创建文件对象(不创建真实文件)
exists()判断文件/目录是否存在
isFile()判断是否是文件
isDirectory()判断是否是目录
createNewFile()创建新文件
mkdir()创建目录
mkdirs()创建多级目录
delete()删除文件/空目录
getAbsolutePath()获取绝对路径
listFiles()列出目录下所有子文件

BufferedWriter(字符输出流,写文件)

方法作用
BufferedWriter(Writer out)构造器,包装一个 Writer
write(String str)写入字符串
newLine()写入换行符(跨平台)
flush()刷新缓冲区,把数据刷到硬盘
close()关闭流

BufferedReader(字符输入流,读文件)

方法作用
BufferedReader(Reader in)构造器,包装一个 Reader
readLine()读取一行,返回 String(读到末尾返回 null)
close()关闭流

ObjectOutputStream(对象序列化输出)

方法作用
ObjectOutputStream(OutputStream out)构造器
writeObject(Object obj)写入对象
close()关闭流

ObjectInputStream(对象序列化输入)

方法作用
ObjectInputStream(InputStream in)构造器
readObject()读取对象(返回 Object,需强转)
close()关闭流

经典代码

1. 写文本文件(BufferedWriter)

▼
java
复制代码
// 目标:把报价单列表写入 data/quotations.txt File file = new File("data/quotations.txt"); file.getParentFile().mkdirs(); // 如果 data 目录不存在,自动创建 // try-with-resources:自动关闭流,不用手动 close() try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { for (SmartHomeQuotation q : quotations) { // 把一条报价单写成一行:编号,项目名称,金额 String line = q.getQuotationNo() + "," + q.getProjectName() + "," + q.calcOriginalTotalAmount(); writer.write(line); writer.newLine(); // 换行 } writer.flush(); // 刷新缓冲区 System.out.println("写入完成,共 " + quotations.size() + " 条"); } catch (IOException e) { e.printStackTrace(); }

double 类型写入 CSV 的陷阱:calcOriginalTotalAmount() 返回 1739723.0,但 Excel 打开时可能隐藏 .0(Excel 把纯数字识别为数值型,自动省略末尾的 .0)。

解决方法:用 String.format 强制保留两位小数(财务规范)

▼
java
复制代码
// 保留两位小数:1739723.00 String amountStr = String.format("%.2f", q.calcOriginalTotalAmount()); // 保留两位小数 + 千分位:1,739,723.00 String amountStr = String.format("%,.2f", q.calcOriginalTotalAmount()); // 写入 writer.write(q.getQuotationNo() + "," + q.getProjectName() + "," + amountStr);

2. 读文本文件(BufferedReader)

▼
java
复制代码
File file = new File("data/quotations.txt"); if (!file.exists()) { System.out.println("文件不存在"); return; } try (BufferedReader reader = new BufferedReader(new FileReader(file))) { String line; while ((line = reader.readLine()) != null) { // 逐行读取,null 表示文件末尾 // 解析一行:Q-SM-001,赵女士全屋智能,54300.0 String[] parts = line.split(","); String quotationNo = parts[0]; String projectName = parts[1]; double amount = Double.parseDouble(parts[2]); System.out.println("编号:" + quotationNo + ",项目:" + projectName + ",金额:" + amount); } } catch (IOException e) { e.printStackTrace(); }

3. 对象序列化(ObjectOutputStream)

▼
java
复制代码
// 把报价单对象完整保存到文件(包括对象里的所有字段) try (ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("data/quotation.obj"))) { oos.writeObject(quotation); // 写入对象 System.out.println("对象已序列化保存"); } catch (IOException e) { e.printStackTrace(); }

4. 对象反序列化(ObjectInputStream)

▼
java
复制代码
try (ObjectInputStream ois = new ObjectInputStream( new FileInputStream("data/quotation.obj"))) { SmartHomeQuotation q = (SmartHomeQuotation) ois.readObject(); // 强转 System.out.println(q.getSummaryLine()); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); }

5. 追加模式写文件(不覆盖原有内容)

▼
java
复制代码
// FileWriter 第二个参数 true = 追加模式 try (BufferedWriter writer = new BufferedWriter( new FileWriter("data/log.txt", true))) { writer.write("新的日志记录"); writer.newLine(); } catch (IOException e) { e.printStackTrace(); }

对象序列化详解

什么是序列化?

程序运行时,对象在内存里。程序一关,内存清空,对象就消失了。序列化就是把内存中的对象变成一串字节,可以存到硬盘文件,也可以通过网络发送给别人。

▼
java
复制代码
// 序列化:对象 → 文件(冻住) ObjectOutputStream.writeObject(q); // 反序列化:文件 → 对象(解冻) SmartHomeQuotation q = (SmartHomeQuotation) ObjectInputStream.readObject();

为什么要实现 Serializable 接口?

Serializable 是一个标记接口(里面没有任何方法)。它的作用就是告诉 JVM:

"这个类里的数据可以安全地转成字节流,你放心序列化。"

如果没有实现:

▼
java
复制代码
public class SmartHomeQuotation { } // ❌ 没实现 Serializable oos.writeObject(q); // ❌ 抛异常:NotSerializableException

实现后:

▼
java
复制代码
public class SmartHomeQuotation implements Serializable { // ✅ private static final long serialVersionUID = 1L; }

哪些类已经实现了 Serializable?

JDK 自带的常用类基本都实现了:

类是否已实现
String✅
Integer、Double、Long 等包装类✅
ArrayList✅
HashMap✅
Date✅

你的自定义类必须自己加上:

▼
java
复制代码
public class SmartHomeQuotation implements Serializable { private static final long serialVersionUID = 1L; } public class Worker implements Serializable { private static final long serialVersionUID = 1L; }

重要:如果 SmartHomeQuotation 里引用了 Person(客户),那么 Person 及其所有子类(Customer、Company)也必须实现 Serializable,否则序列化时会报错。

serialVersionUID 的作用:

想象这是对象的"版本号":

  • 你今天存了一个 SmartHomeQuotation(版本1)
  • 明天你给类加了 remark 字段(变成版本2)
  • 再用版本2去读版本1的文件 → 类结构变了,JVM 不知道怎么办,抛异常

加了 serialVersionUID = 1L,告诉 JVM:"版本兼容,能读就读,不能读的字段忽略。"

完整示例:序列化 + 反序列化

▼
java
复制代码
// 第1步:所有相关实体类必须实现 Serializable public class SmartHomeQuotation implements Serializable { private static final long serialVersionUID = 1L; // ... 字段 ... } public abstract class Person implements Serializable { private static final long serialVersionUID = 1L; } public class Customer extends Person implements Serializable { private static final long serialVersionUID = 1L; } public class Company extends Person implements Serializable { private static final long serialVersionUID = 1L; } // 第2步:序列化写入 public static void serialize(SmartHomeQuotation q, File file) { try (ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream(file))) { oos.writeObject(q); System.out.println("序列化完成:" + file.getAbsolutePath()); } catch (IOException e) { e.printStackTrace(); } } // 第3步:反序列化读取 public static SmartHomeQuotation deserialize(File file) { try (ObjectInputStream ois = new ObjectInputStream( new FileInputStream(file))) { SmartHomeQuotation q = (SmartHomeQuotation) ois.readObject(); System.out.println("反序列化成功:" + q.getQuotationNo()); return q; } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); return null; } }

关键:SmartHomeQuotation 引用了 Person(客户/商家),Person 及其子类(Customer、Company)都必须实现 Serializable,否则 writeObject() 抛 NotSerializableException。


文本文件 vs 对象序列化:怎么选?

两种都能存数据,但适用场景完全不同:

对比文本文件(CSV/txt)对象序列化(.obj)
存什么纯文本字符串完整的对象(字段+类型+对象关系)
人能看懂吗✅ 记事本/Excel 能打开❌ 二进制乱码
别的程序能读吗✅ Excel、Python 都能读❌ 只有 Java 能读
存对象方不方便❌ 手动拼字符串,字段多了很麻烦✅ 一行 writeObject(q) 搞定
嵌套对象能存吗❌ 自己处理引用关系✅ 自动处理对象树
版本兼容性✅ 加字段不影响旧文件❌ 类结构变了可能读不了
文件大小小(纯文本)大(包含类信息)

什么时候用文本文件?

▼
java
复制代码
// 1. 数据要给别的程序用(Excel、Python、别的系统) // 2. 人需要直接看内容(日志、配置) // 3. 数据格式简单,就几个字段 BufferedWriter.write("Q-SM-001,赵女士,50000"); // Excel 能打开

什么时候用序列化?

▼
java
复制代码
// 1. 临时缓存对象,程序重启后还原 // 2. 对象关系复杂(报价单→客户→地址,嵌套多层) // 3. 网络传输对象(A电脑的对象传给B电脑) oos.writeObject(quotation); // 整个对象树一键保存

CSV 的陷阱:字段中包含逗号怎么办?

▼
java
复制代码
// 如果 projectName = "赵女士,全屋智能" // 直接拼接:Q-SM-001,赵女士,全屋智能,50000 // split(",") 后变成 4 段,解析错乱! // 正确做法:字段含逗号时用双引号包裹 // "Q-SM-001","赵女士,全屋智能","50000" // 实际项目不要自己拼 CSV,用第三方库: // Apache Commons CSV、OpenCSV、FastCSV

项目实际选择:

需求推荐方式理由
报价单导出给财务文本文件 CSV财务用 Excel 打开
程序缓存,重启还原序列化一键存取,不用拼字符串
配置文件文本文件 properties人能改
日志记录文本文件 txt人能看

一句话: 要给人看或给别的程序用 → 文本文件;要快速存取 Java 对象 → 序列化。


注意点

  1. try-with-resources 自动关闭流
▼
java
复制代码
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { // 写操作... } // ← 大括号结束自动调用 writer.close()

实现了 AutoCloseable 接口的类都可以这样用。

多个资源时的关闭顺序:

▼
java
复制代码
// ✅ fos 和 writer 都必须放进 try(),否则 fos 没人关 // 关闭顺序:先开后关(writer 先关,再关 fos) try (FileOutputStream fos = new FileOutputStream(file); // 第1个打开 BufferedWriter writer = new BufferedWriter( // 第2个打开 new OutputStreamWriter(fos, "UTF-8"))) { fos.write(bom); // 写 BOM writer.write("内容"); // 写内容 } // ← 先关 writer,再关 fos(栈顺序:先开后关)

fos 不放 try() 的后果:

▼
java
复制代码
// ❌ 错误:fos 在 try 外面 FileOutputStream fos = new FileOutputStream(file); // 打开文件 fos.write(bom); // 写 BOM try (BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(fos, "UTF-8"))) { writer.write("内容"); } // ← writer 关了,但 fos 还开着! // ❌ fos 没人关 → 内存泄漏、文件被锁、数据丢失
  1. flush 与 close 的关系
操作作用什么时候需要
flush()把缓冲区数据刷到硬盘需要立即看到文件内容时(如日志实时写入)
close()先 flush,再关闭流用完流必须调,try-with-resources 自动处理
▼
java
复制代码
// try-with-resources 的 close() 会自动 flush // 所以大部分情况下不需要手动 flush try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { writer.write("内容"); // 不需要手动 flush,close 时会自动刷 } // 只有需要立即落盘时才手动 flush(如实时日志) writer.write("异常日志"); writer.flush(); // 立即写到硬盘,防止崩溃丢失
  1. 流是单向的:要么读,要么写,不能同时读写。

文件编码三种写法对比

核心问题:Java 写的文本文件,Excel 打开可能乱码。

原因:Java 默认 UTF-8,Excel 默认 GBK,两边编码不一致。

三种解决方案对比:

方案代码Excel 双击打开跨平台实际场景
UTF-8(不加 BOM)new OutputStreamWriter(new FileOutputStream(file), "UTF-8")❌ 中文乱码✅ Linux/Mac/Windows 都正常纯后端系统,人不看文件
GBKnew OutputStreamWriter(new FileOutputStream(file), "GBK") 或 new FileWriter(file)✅ 正常显示❌ Linux/Mac 打开乱码国内内部系统,只给 Windows Excel 用
UTF-8 with BOM ✅先写 BOM 头,再用 UTF-8 写内容✅ 正常显示✅ 全平台正常生产环境标准做法

写法1:UTF-8(不加 BOM)

▼
java
复制代码
try (BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(file), "UTF-8"))) { writer.write("编号,项目名称,原始金额,客户姓名"); writer.newLine(); }
  • Excel 双击打开:❌ 乱码(Excel 不知道这是 UTF-8,用 GBK 解码)
  • 需要手动操作:Excel → 数据 → 从文本/CSV → 选择文件 → 编码选 UTF-8

写法2:GBK

▼
java
复制代码
try (BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(file), "GBK"))) { // 或直接用 new FileWriter(file),Windows 默认 GBK writer.write("编号,项目名称,原始金额,客户姓名"); writer.newLine(); }
  • Excel 双击打开:✅ 正常
  • 缺点:Linux/Mac 打开乱码;不支持多语言混合(中英文+日文+韩文)

写法3:UTF-8 with BOM(生产标准)

▼
java
复制代码
FileOutputStream fos = new FileOutputStream(file); // 先写 UTF-8 BOM 头(3个字节),告诉 Excel "这是 UTF-8" fos.write(new byte[]{(byte)0xEF, (byte)0xBB, (byte)0xBF}); try (BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(fos, "UTF-8"))) { writer.write("编号,项目名称,原始金额,客户姓名"); writer.newLine(); // ... 遍历写数据 }
  • Excel 双击打开:✅ 正常(看到 BOM 自动用 UTF-8 解码)
  • Linux/Mac/Windows:✅ 全平台正常
  • 缺点:文件开头多 3 个字节(BOM),对程序读取无影响

BOM 是什么?

BOM(Byte Order Mark)是文件开头的特殊字节序列,像"编码身份证":

  • UTF-8 BOM:EF BB BF(3 个字节)
  • Excel 看到 BOM 就知道"这是 UTF-8 文件,我用 UTF-8 解码"
  • 没有 BOM,Excel 默认用系统编码(Windows = GBK),所以乱码

FileWriter/FileReader 的编码真相:

▼
java
复制代码
// FileWriter 内部其实是这样(用系统默认编码) new FileWriter(file) // ≈ new OutputStreamWriter(new FileOutputStream(file), "系统默认编码") // FileReader 内部其实是这样(用系统默认编码) new FileReader(file) // ≈ new InputStreamReader(new FileInputStream(file), "系统默认编码")

所以 FileWriter 和 FileReader 确实是配对的——都用系统默认编码。

但问题是:系统默认编码会变。

环境系统默认编码
Windows 中文GBK
MacUTF-8
LinuxUTF-8

你今天用 FileWriter(GBK)写的文件,明天在 Mac 上用 FileReader(UTF-8)读 → 乱码。

实际项目中的正确做法:

始终显式指定编码,不用 FileWriter/FileReader。

▼
java
复制代码
// 写入 GBK new OutputStreamWriter(new FileOutputStream(file), "GBK") // 读取 GBK new InputStreamReader(new FileInputStream(file), "GBK") // 写入 UTF-8 new OutputStreamWriter(new FileOutputStream(file), "UTF-8") // 读取 UTF-8 new InputStreamReader(new FileInputStream(file), "UTF-8")

核心原则:写入用什么编码,读取就必须用什么编码。

读取时的编码处理:

▼
java
复制代码
// 读取 GBK 文件 BufferedReader reader = new BufferedReader( new InputStreamReader(new FileInputStream(file), "GBK")); // 读取 UTF-8 文件(带不带 BOM 都可以) BufferedReader reader = new BufferedReader( new InputStreamReader(new FileInputStream(file), "UTF-8")); // 注意:UTF-8 with BOM 的文件,读到的第一行开头会有 \uFEFF 字符 // 如果用 readLine() 读表头,需要去掉它 String header = reader.readLine(); if (header.startsWith("\uFEFF")) { header = header.substring(1); // 去掉 BOM 字符 }

实验验证结果:

写法编码Excel 双击打开原因
UTF-8 无 BOMUTF-8❌ 需要先新建空白文件再导入Excel 不知道编码,默认用 GBK 解码,乱码
GBKGBK✅ 直接打开Windows 系统默认编码
UTF-8 with BOMUTF-8 + BOM 头✅ 直接打开Excel 看到 BOM 头,自动用 UTF-8 解码

一句话结论:

实际项目一律用 UTF-8 with BOM,兼顾跨平台和 Excel 兼容性。


自动检测文件编码(生产环境做法)

用户不懂编码,好的程序应该自动检测。 不能让用户手动选择编码。

检测流程:

▼
text
复制代码
用户上传文件 ↓ 检测前3个字节(BOM) ↓ 有 BOM? ──→ 直接知道编码(UTF-8 / UTF-16) ↓ 没有 用第三方库分析内容 ↓ 返回猜测的编码(GBK / UTF-8 / Big5 等) ↓ 用检测到的编码读取文件

第一步:检测 BOM(不需要第三方库)

▼
java
复制代码
public static String detectByBOM(File file) throws IOException { try (FileInputStream fis = new FileInputStream(file)) { byte[] bom = new byte[3]; int read = fis.read(bom); // UTF-8 BOM:EF BB BF if (read >= 3 && bom[0] == (byte) 0xEF && bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF) { return "UTF-8"; } // UTF-16 BE:FE FF if (read >= 2 && bom[0] == (byte) 0xFE && bom[1] == (byte) 0xFF) { return "UTF-16BE"; } // UTF-16 LE:FF FE if (read >= 2 && bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE) { return "UTF-16LE"; } } return null; // 没有 BOM }

第二步:没有 BOM 时,用 juniversalchardet 猜测

▼
java
复制代码
// Maven 依赖:com.googlecode.juniversalchardet:juniversalchardet:1.0.3 public static String detectByContent(File file) throws IOException { byte[] bytes = Files.readAllBytes(file.toPath()); UniversalDetector detector = new UniversalDetector(null); detector.handleData(bytes, 0, bytes.length); detector.dataEnd(); String encoding = detector.getDetectedCharset(); // "UTF-8" / "GBK" / "GB2312" detector.reset(); return encoding != null ? encoding : "UTF-8"; // 猜不出默认 UTF-8 }

完整检测 + 读取:

▼
java
复制代码
public static void readWithAutoDetect(File file) throws IOException { // 1. 先检测 BOM String encoding = detectByBOM(file); // 2. 没有 BOM,用内容猜测 if (encoding == null) { encoding = detectByContent(file); } System.out.println("检测到编码:" + encoding); // 3. 用检测到的编码读取 try (BufferedReader reader = new BufferedReader( new InputStreamReader(new FileInputStream(file), encoding))) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } }

实际项目中的三种做法对比:

做法场景是否专业
让用户选择编码简单内部工具❌ 用户不懂编码
约定固定编码项目内部系统,统一 UTF-8✅ 但无法处理外部文件
自动检测编码用户上传文件(导入 Excel/CSV)✅ 生产标准

一句话:好的程序不应该问用户"这是什么编码",而应该自己检测出来。


常见坑

  1. 忘记 flush/close,数据没写进硬盘
▼
java
复制代码
BufferedWriter writer = new BufferedWriter(new FileWriter(file)); writer.write("内容"); // ❌ 没 flush 也没 close,数据还在缓冲区,文件可能是空的! // ✅ try-with-resources 自动解决 try (BufferedWriter writer = ...) { ... }
  1. readLine() 返回 null 表示文件结束,不是空字符串
▼
java
复制代码
while ((line = reader.readLine()) != null) { ... } // ✅ while ((line = reader.readLine()).isEmpty()) { ... } // ❌ 末尾抛 NullPointerException
  1. 相对路径 vs 绝对路径
▼
java
复制代码
// 相对路径:相对于程序运行的"工作目录" File file = new File("data/quotations.txt"); // 工作目录通常是项目根目录,所以文件在 项目根目录/data/quotations.txt // 绝对路径:完整路径 File file = new File("C:/JavaProject/zx-plat-practice/data/quotations.txt");

怎么知道工作目录在哪?

▼
java
复制代码
System.out.println(System.getProperty("user.dir")); // 输出:C:\JavaProject\zx-plat-practice
  1. 文件路径分隔符:Windows 用 \,Linux/Mac 用 /。建议用 File.separator 或直接用 /(Java 会自动转)。
▼
java
复制代码
File file = new File("data/quotations.txt"); // ✅ 推荐,跨平台 File file = new File("data\\quotations.txt"); // ❌ Windows 专用
  1. 对象序列化必须实现 Serializable 接口
▼
java
复制代码
public class SmartHomeQuotation implements Serializable { // ✅ 必须! private static final long serialVersionUID = 1L; // 建议加上 }
  1. 反序列化时类结构变了会抛异常:给类加了字段,之前序列化的文件就读不了了。serialVersionUID 可以缓解。

  2. 反序列化时类找不到会抛 ClassNotFoundException:

    • 序列化文件存的是类的完整包名(cn.com.d99714.smart.entity.SmartHomeQuotation)
    • 如果反序列化时类路径变了(比如包名改了、类删了),JVM 找不到类,抛异常
    • 或者 jar 包缺失(比如把文件从 A 项目拷到 B 项目,B 项目没有这个类)
▼
java
复制代码
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { SmartHomeQuotation q = (SmartHomeQuotation) ois.readObject(); } catch (ClassNotFoundException e) { // 类路径变了或类缺失 System.out.println("找不到对应的类:" + e.getMessage()); }

受检异常(Checked Exception)与运行时异常

为什么 write()、readLine() 会强制要求处理异常?

Java 把异常分两类:

类型例子编译器要求为什么这样设计
受检异常IOException、SQLException、FileNotFoundException强制处理! 不处理编译报错这些风险可以预见,写代码时就必须想好出错怎么办
运行时异常NullPointerException、IndexOutOfBoundsException、ArithmeticException不强制,运行时才可能报错通常是代码逻辑错误,理论上可以避免

IOException 为什么是受检异常?

因为读写文件很容易出错:

  • 磁盘满了
  • 文件被别的程序占用
  • 权限不够
  • 硬盘坏了

Java 设计者认为:"这些风险可以预见,你必须在写代码时就考虑好出错了怎么办。"

处理受检异常的两种方式:

方式1:try-catch 自己捕获

▼
java
复制代码
static void writeCSV(BufferedWriter writer) { try { writer.write("内容"); writer.newLine(); } catch (IOException e) { e.printStackTrace(); // 自己处理 } }

方式2:throws 抛给调用方处理

▼
java
复制代码
static void writeCSV(BufferedWriter writer) throws IOException { writer.write("内容"); writer.newLine(); // 如果出错,抛给调用我的人处理 } // 调用方统一捕获 public static void main(String[] args) { try { writeCSV(writer); } catch (IOException e) { e.printStackTrace(); } }

什么时候用哪种?

场景推荐方式理由
工具类方法(被多处调用)throws调用方根据自身场景决定怎么处理
最上层方法(main、Controller)try-catch最后一道防线,必须处理
需要回滚资源(数据库事务)try-catch-finally出错也要释放资源

一句话:受检异常是 Java 强制你"面对风险"。底层方法用 throws 往上抛,顶层方法用 try-catch 兜底。


IDEA 查 API 文档

常用快捷键:

操作IDEA 快捷键效果
查看文档鼠标放类/方法上,按 Ctrl + Q弹出中英文文档说明
跳转到源码Ctrl + B 或 Ctrl + 点击跳转到类的源码实现
查看继承结构Ctrl + H弹出类的继承树
查看方法列表Ctrl + F12弹出当前类的所有方法
全局搜索类双击 Shift搜索任意类名
查看参数提示Ctrl + P方法调用时看参数类型

查文档的标准流程(以 Serializable 为例):

▼
text
复制代码
第1步:光标放在类名上 public class SmartHomeQuotation implements Serializable { ↑ 光标放这里 第2步:按 Ctrl + B 跳转源码 → 跳转到 java.io.Serializable → 看到内容:public interface Serializable { } → 结论:标记接口,无方法,告诉 JVM "这个类可以序列化" 第3步:按 Ctrl + Q 查看文档 → 弹出英文文档 → 看到核心说明: "Serializability of a class is enabled by the class implementing the java.io.Serializable interface." → 结论:实现接口即可启用序列化能力

查 ObjectOutputStream.writeObject 示例:

▼
text
复制代码
第1步:确定类名 → ObjectOutputStream 第2步:Ctrl + B 跳转到类 → 看类描述: "An ObjectOutputStream writes primitive data types and graphs of Java objects to an OutputStream" 第3步:Ctrl + F12 看方法列表 → 找到 writeObject(Object obj) 第4步:点击方法看详细说明 → 参数、返回值、抛出的异常 第5步:回到自己的代码,知道怎么用了

一句话:IDEA 里 Ctrl + B 看源码,Ctrl + Q 看文档。源码是最好的文档。


号外

  • NIO(New IO):JDK 1.4 引入的"新 IO",Path、Files、Channel 等,性能更高,但 API 更复杂。初学先掌握传统 IO。

  • Properties 类:专门读写 .properties 配置文件(键值对格式)。

▼
java
复制代码
// 读取配置文件 Properties props = new Properties(); try (FileInputStream fis = new FileInputStream("config.properties")) { props.load(fis); } String dbUrl = props.getProperty("database.url"); String dbUser = props.getProperty("database.username"); // config.properties 内容: // database.url=jdbc:mysql://localhost:3306/smarthome // database.username=root
  • CSV 格式:逗号分隔值,Excel 可以直接打开,是数据交换的通用格式。

  • File.separator 跨平台写法:

▼
java
复制代码
// 拼接路径时用这个常量,自动适配操作系统 String path = "data" + File.separator + "quotations.txt"; // Windows → "data\\quotations.txt" // Mac/Linux → "data/quotations.txt"
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP