Java8中使用Files.lines跳过损坏的行

10

我正在使用Files.lines(...)读取一个非常大(500mb)的文件。它可以读取部分文件,但在某个点上会因为java.nio.charset.MalformedInputException: Input length = 1而中断。

我认为该文件包含不同字符集的行。有没有办法跳过这些已损坏的行?我知道返回的流是由Reader支持的,我也知道如何跳过Reader,但不知道如何从流中获取Reader以设置我所需的内容。

    List<String> lines = new ArrayList<>();
    try (Stream<String> stream = Files.lines(Paths.get(getClass().getClassLoader().getResource("bigtest.txt").toURI()), Charset.forName("UTF-8"))) {
        stream
            .filter(s -> s.substring(0, 2).equalsIgnoreCase("aa"))
            .forEach(lines::add);
    } catch (final IOException e) {
        // catch
    }
2个回答

17

当预配置的解码器使用异常停止解码时,您无法在解码后过滤带有无效字符的行。 您需要手动配置 CharsetDecoder 以告诉它忽略无效输入或将该输入替换为特殊字符。

CharsetDecoder dec=StandardCharsets.UTF_8.newDecoder()
                  .onMalformedInput(CodingErrorAction.IGNORE);
Path path=Paths.get(getClass().getClassLoader().getResource("bigtest.txt").toURI());
List<String> lines;
try(Reader r=Channels.newReader(FileChannel.open(path), dec, -1);
    BufferedReader br=new BufferedReader(r)) {
        lines=br.lines()
                .filter(s -> s.regionMatches(true, 0, "aa", 0, 2))
                .collect(Collectors.toList());
}

这只是简单地忽略字符集解码错误,跳过这些字符。如果要跳过包含错误的整行内容,您可以让解码器插入替换字符(默认为'\ufffd'),然后过滤掉包含该字符的行:

CharsetDecoder dec=StandardCharsets.UTF_8.newDecoder()
                  .onMalformedInput(CodingErrorAction.REPLACE);
Path path=Paths.get(getClass().getClassLoader().getResource("bigtest.txt").toURI());
List<String> lines;
try(Reader r=Channels.newReader(FileChannel.open(path), dec, -1);
    BufferedReader br=new BufferedReader(r)) {
        lines=br.lines()
                .filter(s->!s.contains(dec.replacement()))
                .filter(s -> s.regionMatches(true, 0, "aa", 0, 2))
                .collect(Collectors.toList());
}

谢谢你的回答,Holger。我也在想是否可以使用流来避免样板代码,但看起来不可能(流是由Reader支持的,我希望能够以某种方式获取Reader并添加解码器)。 - Francesco
2
该API不支持此操作。即使支持,也不会比这里的代码更紧凑。请注意,“样板”只是额外的一行。结果看起来更冗长,只是因为我进行了更慷慨的格式化,因为我不喜欢水平滚动。嗯,当我为更广泛的受众发布代码示例时,我会使用更多的空格,而在我的真实代码中则不会。当然,您也可以内联decpath,并对StandardCharsets.UTF_8以及CodingErrorAction.*Channels.newReaderFileChannel.open等使用import static - Holger
是的,我同意。再次感谢您,Holger。 - Francesco

1
在这种情况下,如果使用流API,解决方案将会更加复杂和容易出错。我建议只使用普通的for循环从BufferedReader中读取数据,然后捕获MalformedInputException异常。这样还可以区分其他IO异常。
List<String> lines = new ArrayList<>();

try (BufferedReader r = new BufferedReader(path,StandardCharsets.UTF_8)){
     try{
          String line = null;
          while((line=r.readLine())!=null){
               if(line.substring(0, 2).equalsIgnoreCase("aa")){
                    lines.add(line);
                }
     }catch(MalformedInputException mie){
           // ignore or do something
     }
}

感谢您的评论,我有几点考虑: *通常使用函数式风格可以使代码更清晰、更简洁。目前代码行数还不多,但如果代码规模增长,我认为函数式编程是正确的选择。 *您正在通过异常进行编程,这是一种我不太喜欢的做法。 *您失去了惰性计算以及由此带来的所有好处(易于并行化、无状态代码等)。说完以上观点后,我要强调,您的解决方案绝对是另一种可行的解决方案。 - Francesco
@Fra 说得好。Holger的答案更详细。然而,我的答案也没有失去“懒惰”,因为它也是懒惰的。但是,Stream的异常处理很复杂。看起来不错,直到你需要调试 :) 此外,在Holgers的解决方案中,你仍然需要捕获和处理IOException。除非你完全忽略它并将其传递上去。最大的问题是使用Stream-filter范例,那段代码非常慢。对于一个500 MB的大文件,我会避免使用过滤器。 - The Coordinator

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接