卷曲引用导致Java Scanner hasNextLine()为false - 为什么?

时间:2013-09-19 17:30:20

标签: java encoding utf-8

我一直遇到一个问题,让java.util.Scanner读取我在记事本中保存的文本文件,即使它可以与其他人一起使用。基本上,当它试图读取问题文件时,它会完全空出来 - hasNextLine()为false,缓冲区为空等等。我把它缩小到它甚至不会读取第一行的事实在文件中是任何位置的卷曲引号。没有异常被抛出。请注意,同一文件上的BufferedReader没有问题。

try {        
    int count = 0;
    Scanner scanner = new Scanner(new File("C:/myfile.txt"));

    while (scanner.hasNextLine()) {
        count++;
        scanner.nextLine();
    }

    scanner.close();
    System.out.print(count);

    count = 0;
    BufferedReader reader = new BufferedReader(new FileReader("C:/myfile.txt"));

    while (reader.readLine() != null) {
        count++;
    }

    reader.close();
    System.out.print(count);
}
catch(IOException e) {
    e.printStackTrace();
}

上面的代码,读取一个只包含单个卷曲引号的文件,打印出“01”。谷歌的搜索让我尝试了这个:

Scanner scanner = new Scanner(new File("C:/myfile.txt"), "ISO-8859-1");

这使它工作(即打印出“11”)。我还注意到,如果我进入记事本并执行另存为...底部的默认编码是“ANSI”。如果我将其更改为“UTF-8”并保存文件,则扫描仪(没有编码)也可以。如果我告诉扫描仪“UTF-8”,那么可以理解它只有在我保存为UTF-8时才有效,但是“ISO-8859-1”似乎使其工作,即使我将其保存为“ANSI”。

所以,我知道它与文件编码有关,但问题是我对文件编码一无所知。我对“ISO-8859-1”的含义非常模糊;无论我如何保存文件,为什么这会使它工作?为什么BufferedReader无论如何都能正常工作?

修改

下面的链接/评论确实帮助我指明了正确的方向!我想我已经明白了。

首先,在记事本中:

  • “ANSI”是CP1252
  • “Unicode”为UTF-16LE
  • “UTF-8”是......好吧,UTF-8

在十六进制中,卷曲撇号表示为:

  • CP1252:92
  • UTF-16LE:1920
  • UTF-8:E2 80 99

根据Charset.defaultCharset(),Java在我的系统上使用的默认编码是UTF-8。因此,当我以UTF-8保存文件时,扫描仪知道会发生什么。但是,当我在CP1252中保存文件时,它一旦达到“92”就会被阻塞,因为它不是表示该编码中字符的有效方式。只要文件中没有任何这样的字符,它就可以正常工作 - “hello world”的十六进制在CP1252和UTF-8中都是相同的,并且不会导致问题。

UTF-8不能与UTF-16文件一起使用,因为它不知道如何处理字节顺序标记(“FFFE”),无论文件中包含哪些字符。

另一方面,当我将扫描仪设置为CP1252或ISO-8859-1时,它更宽容。正如你所知,它并不一定能正确地解释字符,但没有什么可以阻止它识别文件中的行并循环通过。

至于为什么Scanner有问题,但FileReader / BufferedReader没有,我猜这是因为扫描仪需要对文件进行标记,即。解释字符,以便它可以识别空白和其他模式,所以当有无法识别的东西时它会窒息。读者不需要这样做。所有它需要识别的是换行符。

3 个答案:

答案 0 :(得分:4)

如果在创建扫描程序时未指定编码,它将尝试根据字节顺序标记(BOM)来划分编码,这是文件的前几个字节。如果它没有,它将默认为操作系统使用的默认值。由于您使用的是Windows,因此默认为cp-1252。似乎记事本使用ISO-8859-1保存您的文本文件,它与cp-1252类似,但不一样。有关详细信息,请参阅此链接:

http://www.i18nqa.com/debug/table-iso8859-1-vs-windows-1252.html

当您将其保存为UTF-8时,它可能会将UTF-8 BOM放在文件的开头,扫描仪可以接收它。

如果您想更多地了解BOM,请在维基百科中查找 - 文章非常好。您也可以下载PSPad并以十六进制模式打开文本文件以查看单个字节。希望有所帮助:)

答案 1 :(得分:1)

如果在输入文件中遇到编码错误,

Scanner' hasNextLine方法将返回false。没有任何例外。这令人沮丧,即使在JDK 8 documentation中,也无法在任何地方记录。

如果您只想逐行阅读文件,请改用:

final BufferedReader input = new BufferedReader(new InputStreamReader(new FileInputStream("inputfile.txt"), "inputencoding"));

while (true) {
    String line = input.readLine();
    if (line == null) break;
    // process line
}

input.close();

确保上面的inputencoding替换为文件的正确编码。最有可能是utf-8ascii。即使编码不匹配,它也不会像Scanner那样过早终止。

答案 2 :(得分:0)

前段时间我遇到了与用户编辑的配置文件类似的问题。因为我从来不知道编辑器用户会使用什么类型,所以我试试这个:

org.mozilla.universalchardet.UniversalDetector

可从这里获得:

https://code.google.com/p/juniversalchardet/

检测字符编码并不简单,所以我不能确定这个库是否适用于任何条件,但对我来说就足够了。看看,也许会以某种方式检测您的编码,然后将其设置为Scanner