
本文详解如何在 Java 中使用 Scanner 读取文本文件时,准确过滤以 // 开头的注释行、空行及形如 [Section] 的节标识行,并正确解析 CSV 格式的工具数据。
本文详解如何在 java 中使用 scanner 读取文本文件时,准确过滤以 `//` 开头的注释行、空行及形如 `[section]` 的节标识行,并正确解析 csv 格式的工具数据。
在实际开发中,读取配置或数据类文本文件(如带注释和分组标记的 CSV)时,常需跳过非有效数据行——包括注释(如 // 或 #)、空行以及 INI 风格的节头(如 [ElectricTool data])。原始代码中 if (!lineOfText.startsWith("/") && !lineOfText.startsWith("[") || lineOfText.isEmpty()) 存在逻辑错误与语义偏差:
- startsWith("/") 只能匹配以单个 / 开头的行,而示例中注释为 //;
- 逻辑运算符优先级导致条件等价于 (!A && !B) || C,但实际需求是「非空且既不以 // 开头、也不以 [ 开头」——即应使用 && 连接所有保留条件,而非 ||;
- 未处理 [Section] 行中的空格或大小写变体,也缺乏对多行注释的鲁棒性。
✅ 正确做法是:先排除空行,再用正则精准识别需跳过的行模式。推荐使用 java.util.regex.Pattern 定义清晰规则:
// 跳过:空行、以 // 开头的注释行、以 [ 开头的节声明行(支持前后空格)
Pattern ptrnSkip = Pattern.compile("^\s*(?://|\[).*$|^\s*$");该正则含义:
- ^\s*:行首任意空白;
- (?://|\[):非捕获组,匹配 // 或 [;
- .*$:后续任意字符至行尾;
- |^\s*$:或纯空白行(含空字符串);
- 整体覆盖 // comment、[Section]、[Metadata]、` ` 等典型场景。
完整健壮实现如下(含资源自动关闭、字段解析与日志反馈):
立即学习“Java免费学习笔记(深入)”;
public void readToolData() {
Frame myFrame = new Frame();
FileDialog fileName = new FileDialog(myFrame, "Select the file to load", FileDialog.LOAD);
fileName.setDirectory("/");
fileName.setVisible(true);
if (fileName.getDirectory() != null && fileName.getFile() != null) {
String filePath = fileName.getDirectory() + fileName.getFile();
System.out.printf("Selected file: %s%n", filePath);
File fileData = new File(filePath);
Pattern ptrnSkip = Pattern.compile("^\s*(?://|\[).*$|^\s*$");
Pattern ptrnDelim = Pattern.compile("\s*,\s*"); // 智能分割:逗号+可选空格
try (Scanner scnrFile = new Scanner(fileData)) {
while (scnrFile.hasNextLine()) {
String line = scnrFile.nextLine().trim();
// ✅ 关键过滤:仅处理非空、非注释、非节头的有效数据行
if (ptrnSkip.matcher(line).matches()) {
System.out.printf("Skipped: "%s"%n", line);
continue;
}
System.out.printf("Processing: "%s"%n", line);
try (Scanner scnrLine = new Scanner(line).useDelimiter(ptrnDelim)) {
// 假设每行固定8字段:name, code, borrowed, onLoan, cost, weight, rechargeable, power
if (scnrLine.hasNext()) {
String name = scnrLine.next().trim();
String code = scnrLine.next().trim();
int borrowed = Integer.parseInt(scnrLine.next().trim());
boolean onLoan = Boolean.parseBoolean(scnrLine.next().trim());
int cost = Integer.parseInt(scnrLine.next().trim());
int weight = Integer.parseInt(scnrLine.next().trim());
boolean rechargeable = Boolean.parseBoolean(scnrLine.next().trim());
String power = scnrLine.next().trim();
Tool tool = new Tool(name, code, borrowed, onLoan, cost, weight, rechargeable, power);
this.storeTool(tool);
}
} catch (InputMismatchException | NoSuchElementException e) {
System.err.printf("Parse error in line "%s": %s%n", line, e.getMessage());
}
}
} catch (FileNotFoundException e) {
System.err.println("File not found: " + e.getMessage());
}
} else {
System.out.println("No file selected.");
}
}? 关键注意事项:
- 永远优先使用 try-with-resources:确保 Scanner 在异常或正常结束时自动关闭,避免资源泄漏;
- 避免 nextLine() 后直接 nextXXX() 导致的换行符残留问题:本例中每行独立解析,无此风险;
- 字段解析需防御性编程:添加 try-catch 捕获 NumberFormatException/InputMismatchException,防止单行格式错误导致整个读取中断;
- 生产环境建议升级方案:对于复杂 CSV(含引号、换行、转义),应使用专业库如 Apache Commons CSV 或 OpenCSV,它们内置 RFC4180 兼容解析,远超手动正则的可靠性。
通过上述重构,程序将严格按预期跳过所有 // 注释、[Section] 标题及空行,仅解析有效数据行,同时具备可维护性与健壮性。









