Java String.getBytes(charset)和带有两个不同字符集的新String(bytes,charset)

时间:2019-03-15 05:29:33

标签: java string character-encoding

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

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

根据上述内容,据我所知,两个不同方法的字符集参数必须相同,以便新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] = 테스트 

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

5 个答案:

答案 0 :(得分:2)

  

根据上述内容,据我所知,两个不同方法的字符集参数必须相同,以便新的String(字节,字符集)可以返回正确的字符串。

这就是您要针对的目的,以编写正确的代码。但这并不意味着每一个错误的操作都会产生错误的结果。一个简单的示例是仅由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,我不得不求助于Wikipedia

  

统一韩文代码(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混淆,后者可能不会被简单的文本发现。

答案 1 :(得分:1)

  • Java字符串在内部(至少在大多数情况下...)存储为UTF-16。
  • iso-8859-1中的255个字符与其Unicode等效字符具有相同的代码点
  • 我假设您使用某种8位源编码编译了此代码,并且您的String文字最终保留了所有位。 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。那应该会破坏事情(因为现在它将不再使您的位保持完整)
  • 将您的编辑器设置为此韩文编码。字符串文字应该看起来不错。

答案 2 :(得分:0)

@Holger根据要求很好地answer回答了问题。这个问题被很好地表述为调查期间提出的知识问题。尽管如此,它看起来确实像是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太新颖了,无法实现生产。不幸的是,它并不罕见。希望它会被淘汰。)

答案 3 :(得分:0)

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

您说:

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

这根本不是事实。

唯一正确的行是

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

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

修复:对Java源代码始终使用固定字符编码。在pom.xml中使用以下命令进行指定的最简单方法:

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

(或等效于不同的构建系统),并使用了解maven的IDE。

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

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

String originalStr = "테스트";

是支持韩文字符的字符,它们具有相同的输入和输出编码(除非它们互为别名,例如euc-kr和ksc5601) 给出相同的输出(将两者打印到您的控制台并进行比较,或者确保您的控制台使用与Java默认字符集相同的字符集)

答案 4 :(得分:-1)

UTF-8是可变大小的字符集。前128个元素映射为英语。当您将字符移到更高位置时,可以将任何语言的字符映射为最多四个字节。

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