3

据我所知,在 String.getBytes(charset) 中,参数 charset 表示该方法返回编码为给定字符集的字符串的字节。

在 new String(bytes, charset) 中,第二个参数 charset 表示该方法将字节解码为给定的字符集并返回解码结果。

根据以上,据我了解,两种不同方法的 charset 参数必须相同,这样 new String(bytes, charset) 才能返回正确的字符串。(我想这就是我所缺少的。)

我有一个错误解码的字符串,我用这个测试了以下代码:

String originalStr = "Å×½ºÆ®"; // 테스트 
String [] charSet = {"utf-8","euc-kr","ksc5601","iso-8859-1","x-windows-949"};

for (int i=0; i<charSet.length; i++) {
 for (int j=0; j<charSet.length; j++) {
  try {
   System.out.println("[" + charSet[i] +"," + charSet[j] +"] = " + new String(originalStr.getBytes(charSet[i]), charSet[j]));
  } catch (UnsupportedEncodingException e) {
   e.printStackTrace();
  }
 }
}

输出是:

[utf-8,utf-8] = Å×½ºÆ®
[utf-8,euc-kr] = ��쩍쨘�짰
[utf-8,ksc5601] = ��쩍쨘�짰
[utf-8,iso-8859-1] = Å×½ºÆ®
[utf-8,x-windows-949] = 횇횞쩍쨘횈짰
[euc-kr,utf-8] = ?����������
[euc-kr,euc-kr] = ?×½ºÆ®
[euc-kr,ksc5601] = ?×½ºÆ®
[euc-kr,iso-8859-1] = ?¡¿¨ö¨¬¨¡¢ç
[euc-kr,x-windows-949] = ?×½ºÆ®
[ksc5601,utf-8] = ?����������
[ksc5601,euc-kr] = ?×½ºÆ®
[ksc5601,ksc5601] = ?×½ºÆ®
[ksc5601,iso-8859-1] = ?¡¿¨ö¨¬¨¡¢ç
[ksc5601,x-windows-949] = ?×½ºÆ®
[iso-8859-1,utf-8] = �׽�Ʈ
[iso-8859-1,euc-kr] = 테스트
[iso-8859-1,ksc5601] = 테스트
[iso-8859-1,iso-8859-1] = Å×½ºÆ®
[iso-8859-1,x-windows-949] = 테스트
[x-windows-949,utf-8] = ?����������
[x-windows-949,euc-kr] = ?×½ºÆ®
[x-windows-949,ksc5601] = ?×½ºÆ®
[x-windows-949,iso-8859-1] = ?¡¿¨ö¨¬¨¡¢ç
[x-windows-949,x-windows-949] = ?×½ºÆ®

如您所见,我弄清楚了获取原始字符串的方法:

[iso-8859-1,euc-kr] = 테스트  
[iso-8859-1,ksc5601] = 테스트  
[iso-8859-1,x-windows-949] = 테스트 

怎么可能?如何将字符串正确编码和解码为不同的字符集?

4

5 回答 5

3

根据以上,据我了解,两种不同方法的 charset 参数必须相同,这样 new String(bytes, charset) 才能返回正确的字符串。

这就是您应该针对的目标,即编写正确的代码。但这并不意味着每次错误的操作都会产生错误的结果。一个简单的示例是仅由 ASCII 字母组成的字符串。许多编码为这样的字符串生成相同的字节序列,因此仅使用这样的字符串的测试不足以发现与编码相关的错误。

如您所见,我弄清楚了获取原始字符串的方法:

[iso-8859-1,euc-kr] = 테스트  
[iso-8859-1,ksc5601] = 테스트  
[iso-8859-1,x-windows-949] = 테스트 

怎么可能?如何将字符串正确编码和解码为不同的字符集?

好吧,当我执行

System.out.println(Charset.forName("euc-kr") == Charset.forName("ksc5601"));

在我的机器上,它打印true. 或者,如果我执行

System.out.println(Charset.forName("euc-kr").aliases());

它打印

[ksc5601-1987, csEUCKR, ksc5601_1987, ksc5601, 5601, euc_kr, ksc_5601, ks_c_5601-1987, euckr]

所以对于euc-krksc5601,答案很简单。这些是相同字符编码的不同名称。

对于x-windows-949,我不得不求助于维基百科

统一韩文代码 (UHC) 或扩展 Wansung,在 Microsoft Windows 下也称为代码页 949(Windows-949、MS949 或含糊不清的 CP949),是韩语的 Microsoft Windows 代码页。它是 Wansung Code(KS C 5601:1987,编码为 EUC-KR)的扩展,包括 Johab 中存在的所有 11172 个韩文音节(KS C 5601:1992 附件 3)。

所以它是一个扩展ksc5601,只要你不使用任何受扩展影响的字符(想想上面的 ASCII 示例),它就会导致相同的结果。

通常,这不会使您的前提无效。只有在双方使用相同的编码时,才能保证正确的结果。这只是意味着,测试代码要困难得多,因为它需要足够的测试输入数据来发现错误。例如,西方世界的一个常见错误是将 iso-latin-1 (ISO 8859-1) 与 Windows 代码页 1252 混淆,这可能不会被简单的文本发现。

于 2019-03-15T12:54:36.993 回答
1
  • Java 字符串在内部(至少在大多数情况下......)存储为 UTF-16。
  • iso-8859-1 中的 255 个字符与其对应的 Unicode 字符具有相同的代码点
  • 我假设您使用一些 8 位源编码编译了此代码,并且您的字符串文字最终所有位都完好无损。Java 认为它现在有 UTF-16,但实际上它有垃圾字符,每个字符都在 0x00 到 0xFF 的范围内。
  • 当您要求 Java 将其“UTF-16”写为 iso-8859-1 时,它只是直接写出所有这些字节(因为代码点是共享的)。如果您编写为其他编码,则需要转换其中的一些。如果您有任何超出单字节范围的字符,您会得到一个字符?(因为它们不能用 iso-8859-1 表示)。
  • 所以你的 iso-8859-1 字节不是 iso-8859-1,但它们仍然有你原来的位
  • 当您将其作为 iso-8859-1 读回时,它将保持“垃圾”
  • 但是,当您使用它实际代表的韩语编码读回它时,您会得到正确的文本

“您的 iso-8859-1 字节不是 iso-8859-1”

好吧,如果有人确实想写“Å×½ºÆ®”并使用 iso-8859-1,他们会得到与你完全相同的字节。所以在某种程度上,它仍然是完全有效的 iso-8859-1。如果不是,Java 会?为该编码中不存在的字符添加一些字符。


您可以尝试两件事:

  • 将源代码编码设置为 UTF-8。这应该会破坏事情(因为现在它不会再让你的位保持完整了)
  • 将您的编辑器设置为此韩语编码。字符串文字应该看起来不错。
于 2019-03-15T10:04:44.730 回答
0

@Holger 对所提出的问题给出了很好的回答。这个问题很好地表述为在调查过程中得出的知识问题。尽管如此,它似乎确实是一个 XY 问题。

“Å×½ºÆ®”如何​​代表“테스트”?

正如已经发现的那样,ISO 8859-1 中的“Å×½ºÆ®”在韩文脚本的一些字符编码中与“테스트”的字节序列相同:

C5 D7 BD BA C6 AE 

没有文本,只有编码文本。

在传递文本时,必须在发送字节的同时了解所使用的字符编码。因此,为了交流테스트,人们将发送字节 C5 D7 BD BA C6 AE 以及它们表示文本编码的理解,例如 Windows-949。这显然不是所做的。

有时,当需要在文本数据类型中处理字节序列时,会使用字节到字符方案。一个是Base64。它一次占用 3 个字节,并用四个字符表示。在传达这样的用法时,字符串和对正在使用的 Base64 以及字节应该代表什么的理解。

有时 Base64 被认为是浪费的,并且它只使用几乎每个字符集中都存在的有限可打印字符集的属性不受重视,因此使用了更紧凑的方案。我称之为Base256。它一次占用 1 个字节,并用一个字符表示。它使用与 ISO 8859-1 字符编码相同的映射。

综上所述,通信失败。缺少以下元数据:

  • 字符串“Å×½ºÆ®”表示可以通过 ISO 8859-1“编码”获得的字节序列。
  • 该字节序列表示使用 Windows-949 编码的文本。

(我认为 Base256 太新奇了。不幸的是,它并不少见。希望它会停止使用。)

于 2019-03-16T22:40:41.877 回答
0

您的问题是代码中的初始假设不正确。

你说:

String originalStr = "Å×½ºÆ®"; // 테스트 

这根本不是真的。

唯一正确的行是

String originalStr = "테스트"; // 테스트 

originalString不包含字符 테스트。您刚刚找到了一个编码,当给定输入 string 时Å×½ºÆ®,它将向您的终端发送具有您未提及的特定字符编码的字节,这导致显示 테스트 。

修复:始终为您的 Java 源代码使用固定的字符编码。在 pom.xml 中指定它的最简单方法是:

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

(或不同构建系统的等效项)并使用理解 maven 的 IDE。

否则,您需要确保在 IDE 或编辑器中使用与编译源代码时相同的字符编码。或者,您可以坚持只\u对非 ASCII 字符使用 Unicode 转义字符。

设置完成后,您会注意到输入的编码对:

String originalStr = "테스트";

是那些支持韩文字符并且具有相同输入和输出编码的那些(除非那些仅仅是彼此的别名,例如 euc-kr 和 ksc5601)提供相同的输出(打印您的控制台并比较它们,或确保您的控制台与 Java 默认字符集的字符集相同)

于 2019-03-22T02:52:46.547 回答
-1

UTF-8 是一个可变大小的字符集。前 128 个元素映射到英语。随着字符的增加,任何语言的字符最多可以映射为四个字节。

与此相比,大多数其他字符集都是固定大小的字符集,其中大多数是两字节字符集。因此,当您将字节流从一个字符集映射到时,您会看到重叠。例如,英文字符“A”将在 UTF-8 中表示为 0x41,在 unicode 中表示为 0x0041。因此,如果您采用 unicode 编码的字节流并尝试将其解码为 UTF-8,您会发现两个字符,一个 NUL,然后是一个“A”。

于 2019-03-15T05:58:38.507 回答