ISO-8859-1编码与二进制数据保留

9

我在@Esailija回答我的问题的评论中读到:

ISO-8859-1是唯一完全保留原始二进制数据,具有精确字节<->码点匹配的编码

我还在@AaronDigulla的答案中读到:

在Java中,ISO-8859-1(又名ISO-Latin1)是一种1:1映射

我需要一些关于这个的见解。这将会失败(如此处所示here):

// \u00F6 is ö
System.out.println(Arrays.toString("\u00F6".getBytes("utf-8")));
// prints [-61, -74]
System.out.println(Arrays.toString("\u00F6".getBytes("ISO-8859-1")));
// prints [-10]

问题

  1. 我承认我不太明白-为什么它不能在上面的代码中获取字节?
  2. 最重要的是,这个(保留字节行为的ISO-8859-1)是在哪里指定的 - 链接到源或JSL会很好。它是唯一具有此属性的编码吗?
  3. 这与ISO-8859-1是默认设置有关吗?

另请参见此问题以获取其他字符集的良好反例。


-10 是 0xF6 的有符号十进制值。尝试 byte b = -10; System.out.println(Integer.toHexString(b & 0xFF)); - JB Nizet
@JBNizet:我知道这个 - 我所询问的是在Java中引用的“ISO-8859-1是唯一完全保留原始二进制数据的编码” - 这就是我不太明白的地方 - 你提供的确实打印了f6 - "\u00F6"包含的字节是什么?[-61,-74]还是[-10]? - Mr_and_Mrs_D
你刚才说过,你知道-10和f6是同一个字节,只是以不同的方式表示。因此,0x00F6中包含的字节是0和F6(16进制),或者是有符号的10和-10。 - JB Nizet
@JBNiz:为什么它不打印[0,-10] - 假设ISO-8859-1是字节保留的 - 这就是我不理解的地方 - 在Java中报告的“字节保留”行为。 - Mr_and_Mrs_D
好的。现在我明白你的意思了。请看我的回答。 - JB Nizet
2个回答

14

"\u00F6" 不是字节数组,它是包含单个字符的字符串。请执行以下测试:

public static void main(String[] args) throws Exception {
    byte[] b = new byte[] {(byte) 0x00, (byte) 0xf6};
    String s = new String(b, "ISO-8859-1"); // decoding
    byte[] b2 = s.getBytes("ISO-8859-1"); // encoding
    System.out.println("Are the bytes equal : " + Arrays.equals(b, b2)); // true
}

为了检查这对于任何字节都是正确的,只需改进代码并循环遍历所有字节:

public static void main(String[] args) throws Exception {
    byte[] b = new byte[256];
    for (int i = 0; i < b.length; i++) {
        b[i] = (byte) i;
    }
    String s = new String(b, "ISO-8859-1");
    byte[] b2 = s.getBytes("ISO-8859-1");
    System.out.println("Are the bytes equal : " + Arrays.equals(b, b2));
}

ISO-8859-1是一种标准编码方式,因此使用的语言(Java、C#或其他)并不重要。

这里有一个维基百科参考链接,它声称覆盖了每个字节:

1992年,IANA注册了字符映射ISO_8859-1:1987,更常用的MIME名称为ISO-8859-1(请注意ISO 8859-1上面的额外连字符),它是ISO 8859-1的超集,用于在互联网上使用。这个映射将C0和C1控制字符分配给未分配的代码值,因此通过每个可能的8位值提供256个字符。

(强调我的)


谢谢 - 这回答了1个问题,我真的需要一些权威的链接来确定/否认2) - 或一些解释 - 即 ISO-8859-1 的字节保留行为以及它是唯一的(在Java和可能其他语言中,我猜像C#)。有关其他字符集,请参见:https://dev59.com/NHE85IYBdhLWcg3w64IA - Mr_and_Mrs_D
1
我不知道它是否是唯一的,不确定。 - JB Nizet
1
@Mr_and_Mrs_D 这是唯一一个解码后的代码点值也等于其解码自的字节值(\u00F6 <-> 0xF6)的情况。这就是我的意思。所以如果你有解码后的字符串和编码的字节,那么对于任意二进制数据,(byte)str.charAt(i) == bytes[i] 总是成立的,其中 str 是使用 "ISO-8859-1" 创建的新字符串。 - Esailija
1
@Mr_and_Mrs_D 这也是一种罕见的属性,但不是 ISO-8859-1 所独有的,它可以保证对于任意二进制数据进行无损往返转换:字节 -> 字符串 -> 字节。 - Esailija
我在维基百科的ISO-8859-1页面上忽略了那个信息。感谢您强调它。 - Ludovic Kuty
显示剩余2条评论

6
为了保留原始的二进制数据,编码需要将每个唯一的字节序列映射到一个唯一的字符序列。
这就排除了所有多字节编码(如UTF-8/16/32、Shift-Jis、Big5等),因为它们中不是每个字节序列都有效,因此会解码为某些替换字符(通常为?或�)。在字符串已经被解码成替换字符之后,无法从中得知是什么导致了替换字符。
另一个选项是忽略无效的字节,但这也意味着无限数量的不同字节序列解码成相同的字符串。你可以在字符串中用十六进制编码替换无效的字节,例如“0xFF”。但无法判断原始字节是否合法地解码成了“0xFF”,所以这也行不通。
这就只剩下了8位编码,其中每个序列只有一个字节。如果存在映射,则单个字节是有效的。但许多8位编码存在漏洞,无法编码256个不同的字符。
为了保留原始的二进制数据,您需要一个编码能编码256个不同字符的8位编码。ISO-8859-1并不是唯一的选择。但它独特的地方在于,解码后的代码点值也是字节值。
因此,如果你有解码后的字符串和编码的字节,它们总是匹配的。
(byte)str.charAt(i) == bytes[i] 

对于任意二进制数据,其中strnew String(bytes, "ISO-8859-1")bytesbyte[]


这也与Java无关。我不知道他的评论意味着什么,这些是字符编码的属性,而不是编程语言。


这个评论的目的是从代码的角度来看待这一切(还要注意“在Java中,ISO-8859-1(又名ISO-Latin1)是一种1:1映射”)-也就是说,我不知道所有这些在C中会是什么样子-非常有启发性的答案@JBNizet(“但它独特之处在于,解码后的代码点值也是它解码自的字节值”)+1 :) - Mr_and_Mrs_D

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