
本文详解如何修复因 scanner 未初始化导致的 nullpointerexception,并实现一个健壮的 csv 解析器,用于统计信用卡交易总数、借记/贷记笔数及借记总金额。
本文详解如何修复因 scanner 未初始化导致的 nullpointerexception,并实现一个健壮的 csv 解析器,用于统计信用卡交易总数、借记/贷记笔数及借记总金额。
在开发 CSV 读取器分析信用卡数据时,NullPointerException 是初学者最常见的陷阱之一——其根源往往在于对象使用早于初始化。你的代码中,private Scanner s; 仅声明而未初始化,却在 scanner() 方法开头就调用 s.nextLine(),此时 s 为 null,必然触发异常。
✅ 正确的初始化时机与流程
应将 Scanner 初始化移至构造函数或 scanner() 方法起始处,确保在任何读取操作前完成实例化。同时,需明确区分“文件打开失败”与“文件已读完”两类异常:FileNotFoundException 表示文件不存在;NoSuchElementException 表示无更多行可读——二者不可混淆处理。
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
以下是重构后的核心逻辑(含完整功能扩展):
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class CsvReader {
private Scanner s;
private int totalTransactions = 0;
private int debitCount = 0;
private int creditCount = 0;
private double totalDebitAmount = 0.0;
// 构造函数:立即初始化 Scanner(若文件存在)
public CsvReader(String fileName) {
try {
s = new Scanner(new File(fileName));
} catch (FileNotFoundException e) {
System.err.println("❌ 错误:文件 '" + fileName + "' 未找到。请检查路径和文件名。");
s = null; // 显式设为 null,避免后续误用
}
}
// 主解析方法
public void analyze() {
if (s == null) return; // 文件加载失败,直接退出
while (s.hasNextLine()) {
String line = s.nextLine().trim();
if (line.isEmpty()) continue; // 跳过空行
String[] fields = getTokens(line);
if (fields.length < 5) {
System.err.println("⚠️ 警告:跳过格式异常行(字段不足5个): " + line);
continue;
}
// 假设第3列为交易类型("Debit" / "Credit"),第5列为金额(如 "$123.45")
String type = fields[2].trim();
String amountStr = fields[4].trim().replace("$", "").replace(",", "");
try {
double amount = Double.parseDouble(amountStr);
totalTransactions++;
if ("Debit".equalsIgnoreCase(type)) {
debitCount++;
totalDebitAmount += amount;
} else if ("Credit".equalsIgnoreCase(type)) {
creditCount++;
}
} catch (NumberFormatException e) {
System.err.println("⚠️ 警告:金额解析失败,跳过该行: " + line);
}
}
s.close(); // ✅ 关闭资源,防止内存泄漏
printSummary();
}
// 你原有的 CSV 解析逻辑(已优化:避免空指针 & 提升鲁棒性)
private String[] getTokens(String input) {
String[] tokens = new String[5];
for (int i = 0; i < tokens.length; i++) tokens[i] = "";
int index = 0, pointer = 0;
boolean inQuotes = false;
while (pointer < input.length()) {
char c = input.charAt(pointer);
if (c == '"') {
inQuotes = !inQuotes;
} else if (c == ',' && !inQuotes) {
index++;
if (index >= tokens.length) break; // 防越界
} else {
tokens[index] += c;
}
pointer++;
}
return tokens;
}
private void printSummary() {
System.out.println("? 交易统计报告");
System.out.println("├─ 总交易数: " + totalTransactions);
System.out.println("├─ 借记交易数: " + debitCount);
System.out.println("├─ 贷记交易数: " + creditCount);
System.out.printf("└─ 借记总金额: $%.2f%n", totalDebitAmount);
}
// 使用示例
public static void main(String[] args) {
CsvReader reader = new CsvReader("visadata.txt");
reader.analyze();
}
}⚠️ 关键注意事项
- 永远先检查 s != null:在调用 hasNextLine() 或 nextLine() 前,确保 Scanner 已成功初始化;
- 及时关闭资源:s.close() 必须执行,否则可能引发文件句柄泄漏;
- 字段索引需与实际 CSV 结构对齐:示例假设第3列是类型、第5列是金额,请根据真实数据调整 fields[2] 和 fields[4];
- 金额字符串预处理:移除 $、, 等非数字字符后再 parseDouble,避免 NumberFormatException;
- 空行与异常行容错:添加 trim() 和长度校验,提升程序健壮性。
通过以上重构,你不仅能彻底规避 NullPointerException,还能获得一个可扩展、易维护、生产就绪的信用卡 CSV 分析工具。


















