Java Iterator遍历CSV时跳过半数元素问题求助
根据你描述的现象(每3行仅读取1行,最终HashMap数据量减半)和提供的代码、CSV样本,我整理出几个最可能的原因及对应的解决办法:
1. CSV解析器误将多行合并为一行(核心嫌疑)
你的调试日志显示每次迭代跳过2行,直接读取第3行,这大概率是CSV解析时未正确处理字段中的特殊字符导致多行被合并成一行。比如:
- 某些字段(如Country或Language)包含未被引号包裹的换行符、逗号等;
- Adobe的
Csv类默认配置未适配你的CSV格式(比如引号字符、分隔符设置错误)。
解决办法:
a. 验证CSV解析结果
在循环中打印每一行的解析内容,确认是否存在多行合并的情况:
while (rowIterator.hasNext()) { String[] row = rowIterator.next(); System.out.println("当前解析行的字段数:" + row.length); // 打印所有字段,看是否包含多行内容 for (int i = 0; i < row.length; i++) { System.out.printf("row[%d] = %s%n", i, row[i]); } }
如果某一行的字段数远大于7(你的CSV表头有7列),说明确实存在多行合并的问题。
b. 显式配置Csv类的解析规则
默认的Csv实例可能没有启用引号解析,导致无法识别包含特殊字符的字段。显式配置引号和分隔符:
Csv csv = new Csv(); csv.setQuote('"'); // 设置CSV字段的引号包裹字符 csv.setSeparator(','); // 明确指定分隔符为逗号 csv.setTrimValues(true); // 可选:去除字段前后的空格
2. HashMap键重复导致数据被覆盖
你的键生成逻辑是locale.toLowerCase() + "_" + country.toLowerCase(),其中country取自row[1](ISO639-2 Country Code),locale取自row[4](ISO639-2 Lang)。如果CSV中存在相同国家代码+相同语言代码的多行数据,HashMap.put()会直接覆盖旧值,导致最终数据量远小于总行数。
解决办法:
a. 检查键的唯一性
在put前打印重复键的日志,确认是否存在覆盖:
String key = locale.toLowerCase() + "_" + country.toLowerCase(); if (localeToFormat.containsKey(key)) { System.out.printf("发现重复键:%s,旧值:%s,新值:%s%n", key, localeToFormat.get(key), dateFormat); } localeToFormat.put(key, dateFormat);
如果存在大量重复键,说明你需要调整键的生成规则(比如加入row[2]国家名称,或者使用更唯一的组合),或者改用HashMap<String, List<String>>存储多个值。
b. 修正字段索引错误(可选)
看你的CSV表头,row[2]才是实际的Country名称(比如"Albania"),而你当前用的row[1]是ISO639-2国家代码(比如"AL")。如果这不是你的预期,可能导致键的逻辑错误,建议修正索引:
String country = row[2]; // 改用实际国家名称 String locale = row[4]; String dateFormat = row[6];
3. 未跳过表头导致第一条数据错误
你的调试提到“表头已被跳过”,但代码中没有任何跳过表头的逻辑。如果Csv.read()默认不跳过表头,那么第一行(表头)会被当成数据行存入HashMap,同时占用一个条目。
解决办法:
在循环前显式跳过表头:
Iterator<String[]> rowIterator = csv.read(is, StandardCharsets.UTF_8.name()); // 跳过表头行 if (rowIterator.hasNext()) { rowIterator.next(); } // 开始处理数据行 while (rowIterator.hasNext()) { // ... 你的逻辑 }
4. Adobe Csv.read()方法的代码错误
你提供的AdobeCsv.read()方法存在变量遮蔽问题:
InputStream in = new BufferedInputStream(in, 4096); // 局部变量in遮蔽了参数in
这行代码会导致构造BufferedInputStream时使用未初始化的局部变量in,引发NullPointerException或流读取异常。如果这是实际运行的代码,必须修复:
public Iterator<String[]> read(InputStream in, String charset) throws IOException { if (charset == null) { charset = System.getProperty("file.encoding"); } // 重命名局部变量,避免遮蔽参数 BufferedInputStream bufferedIn = new BufferedInputStream(in, 4096); this.input = new InputStreamReader(bufferedIn, charset); return this.read(); }
额外排查步骤
- 验证流的完整性:将CSV流写入字节数组,检查长度是否与文件实际大小一致,确认流没有被提前截断:
ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { baos.write(buffer, 0, bytesRead); } byte[] csvContent = baos.toByteArray(); System.out.println("CSV文件字节长度:" + csvContent.length); // 使用字节数组重新构建流进行解析 Iterator<String[]> rowIterator = csv.read(new ByteArrayInputStream(csvContent), StandardCharsets.UTF_8.name());
- 检查换行符兼容性:虽然你说换行符正确,但可以尝试将CSV文件转换为LF(Unix)格式,避免Windows CRLF格式可能带来的解析问题。
内容的提问来源于stack exchange,提问作者Jorel Amthor

