2009-01-31 26 views
6

我有一个文件,它被编码为iso-8859-1,并且包含诸如ô的字符。Java应用程序:无法正确读取iso-8859-1编码文件

我读书用java代码,像这样的文件:

File in = new File("myfile.csv"); 
InputStream fr = new FileInputStream(in); 
byte[] buffer = new byte[4096]; 
while (true) { 
    int byteCount = fr.read(buffer, 0, buffer.length); 
    if (byteCount <= 0) { 
     break; 
    } 

    String s = new String(buffer, 0, byteCount,"ISO-8859-1"); 
    System.out.println(s); 
} 

然而ô角色总是出现乱码,通常打印作为? 。

我已经读过这个主题(并且在学习中学到了一些东西),例如

但仍不能得到这个工作

有趣的是这部作品在我的本地PC(XP),但不是我的Linux中。

我已经检查了我的JDK支持所需字符集(它们是标准的,所以这是没有惊喜)使用:

System.out.println(java.nio.charset.Charset.availableCharsets()); 
+0

我应该补充一点,我能够正确地使用我的linux终端看到字符或原始文件,如果我只是简单地捕捉文件的内容 – Joel 2009-01-31 11:45:08

+0

终端正在使用什么字符编码? – McDowell 2009-01-31 11:59:08

+0

有趣的是,如果我添加运行时Java属性“-Dfile.encoding = UTF16”,它可以按预期工作,但我不明白为什么这应该重要 - 我不认为它是一个解决方案,但更多的是黑客。它不适用于设置为UTF8的属性。 – Joel 2009-01-31 12:55:30

回答

12

我怀疑你的文件不是实际上是编码为ISO-8859-1,或者System.out不知道如何打印字符。

我建议检查第一个,检查文件中的相关字节。要检查第二,检查字符串中的字符有关,它打印出来与

System.out.println((int) s.getCharAt(index)); 

在这两种情况下,结果应该是244小数; 0xf4十六进制。

查看my article on Unicode debugging的一般建议(所提供的代码是用C#编写的,但很容易转换为Java,原理相同)。

一般来说,顺便说一下,我会用正确的编码将InputStreamReader打包到流中 - 这比“手动”创建新字符串更容易。我意识到这可能只是演示代码。

编辑:这里是一个非常简单的方法来证明控制台是否会工作:

System.out.println("Here's the character: \u00f4"); 
3

如果可以,尽量在调试器中运行你的程序,看看有什么是你的内's'字符串创建后。它有可能是正确的内容,但输出在System.out.println(s)调用后出现乱码。在这种情况下,Java认为输出的编码和Linux上终端/控制台的字符编码之间可能存在不匹配。

9

解析文件作为一个字节的固定大小的块不好---如果有些什么人物都有跨越两个块的字节表示?使用一个InputStreamReader用合适的字符编码来代替:

BufferedReader br = new BufferedReader(
     new InputStreamReader(
     new FileInputStream("myfile.csv"), "ISO-8859-1"); 

char[] buffer = new char[4096]; // character (not byte) buffer 

while (true) 
{ 
     int charCount = br.read(buffer, 0, buffer.length); 

     if (charCount == -1) break; // reached end-of-stream 

     String s = String.valueOf(buffer, 0, charCount); 
     // alternatively, we can append to a StringBuilder 

     System.out.println(s); 
} 

顺便说一句,记得检查Unicode字符确实可以正确显示。您也可以将程序输出重定向到一个文件,然后将其与原始文件进行比较。

由于Jon Skeet暗示,问题也可能与控制台有关。尝试System.console().printf(s)以查看是否有差异。

1

基本上,如果它在你的本地XP PC上工作,但不在Linux上,并且你正在解析完全相同的文件(即你在盒子之间以二进制方式传输它),那么它可能与System.out.println调用。我不知道你是如何验证输出的,但是如果你通过从XP机箱连接远程shell来完成,那么就要考虑shell(和客户机)的字符集。

此外,Zach Scrivena建议的内容也是如此 - 你不能假设你可以用这种方式从数据块中创建字符串 - 要么使用InputStreamReader,要么先读取完整的数据到数组中(显然不适用于一个大文件)。但是,因为它似乎在XP上工作,那么我敢打赌,这可能不是你在这个特定情况下的问题。

6

@Joel - your own answer确认问题是操作系统上的默认编码(UTF-8,一个Java选取的)和您的终端正在使用的编码(ISO-8859-1)之间的差异。

考虑以下代码:

public static void main(String[] args) throws IOException { 
    byte[] data = { (byte) 0xF4 }; 
    String decoded = new String(data, "ISO-8859-1"); 
    if (!"\u00f4".equals(decoded)) { 
     throw new IllegalStateException(); 
    } 

    // write default charset 
    System.out.println(Charset.defaultCharset()); 

    // dump bytes to stdout 
    System.out.write(data); 

    // will encode to default charset when converting to bytes 
    System.out.println(decoded); 
} 

默认情况下,我的Ubuntu(8.04)终端使用UTF-8编码。通过此编码,可以打印:

UTF-8
?&#x00F4;

如果我切换终端的编码为ISO 8859-1,这是印刷:

UTF-8
&#x00F4;&#x00C3;&#x00B4;

在两种情况下,相同的字节被发射由Java程序:

5554 462d 380a f4c3 b40a 

唯一的区别是在该终端是如何解释它接收到的字节。在ISO 8859-1中,&#x00F4;被编码为0xF4。在UTF-8中,&#x00F4;被编码为0xC3B4。其他字符对于这两种编码都是通用的。

相关问题