【问题标题】:Different UTF-16 Encoding in Java versus C#Java 与 C# 中的不同 UTF-16 编码
【发布时间】:2016-03-14 01:47:51
【问题描述】:

在 C# 与 Java 中将字符串转换为字节时,我遇到了不同的结果。

C#:

byte[] byteArray =  Encoding.Unicode.GetBytes ("chess ¾");
for (int i = 0; i < byteArray.Length; i++)
    System.Diagnostics.Debug.Write (" " + byteArray[i]);
System.Diagnostics.Debug.WriteLine("");
System.Diagnostics.Debug.WriteLine(Encoding.Unicode.GetString(byteArray));

显示:

99 0 104 0 101 0 115 0 115 0 32 0 190 0
chess ¾

Java:

byte[] byteArray = "chess ¾".getBytes("UTF-16LE");
for (int i = 0; i < byteArray.length; i++)
        System.out.print(" " + (byteArray[i]<0?(-byteArray[i]+128):byteArray[i]));
System.out.println("");
System.out.println(new String(byteAppName,"UTF-16LE"));

显示:

99 0 104 0 101 0 115 0 115 0 32 0 194 0
chess ¾

注意字节数组中倒数第二个值是不同的!我的目标是加密这些数据并能够从 C# 或 Java 读取它。这种差异似乎是一个障碍。

附带说明一下,在我学会使用 Unicode(C#)/UTF-16LE(Java) 之前,我使用的是 UTF-8 ...

C#: byte[] byteArray = Encoding.UTF8.GetBytes ("chess ¾");

显示: 99 104 101 115 115 32 194 190

Java: byteArray = appName.getBytes("UTF-8");

显示: 99 104 101 115 115 32 190 194

奇怪的是,倒数第二个和最后一个字节被翻转。

最后,¾ 的 Unicode 是十进制 190 (http://www.fileformat.info/info/unicode/char/BE/index.htm),而不是十进制 194 (Â) (http://www.fileformat.info/info/unicode/char/00c2/index.htm)。

任何帮助将不胜感激。

【问题讨论】:

  • 好奇 - 如果您手动将字节数组从一个字节数组放入另一个字节数组(即:尝试解码字节),您会得到什么样的输出?
  • 哦,欢迎来到 StackOverflow!这是一个很好的第一个问题:)
  • 好问题:在 Java 中:byteArray = new byte[] {99, 0, 104, 0, 101, 0, 115, 0, 115, 0, 32, 0, -62, 0}; 显示:99 0 104 0 101 0 115 0 115 0 32 0 190 0chess Â
  • 在 C# 中:byte[] byteArray = new byte[] {99, 0, 104, 0, 101, 0, 115, 0, 115, 0, 32, 0, 194, 0}; 显示:99 0 104 0 101 0 115 0 115 0 32 0 194 0chess Â
  • @Chris - 感谢您的回复。非常感谢任何见解。

标签: java c# unicode


【解决方案1】:

您的问题不在于编码,而在于您打印结果的方式,您正在使用 byteArray[i] &lt; 0 ? (-byteArray[i] + 128) : byteArray[i] 从字节转换为整数,这会给您错误的结果,请改用 byteArray[i] &amp; 0xFF 之类的其他内容。使用此 poc 比较两种转换:

    String encoding = "UTF-16LE";
    byte[] byteArray = "chess ¾".getBytes(encoding);
    for (int i = 0; i < byteArray.length; i++) {
        // your conversion
        System.out.print(" " + (byteArray[i] < 0 ? (-byteArray[i] + 128) : byteArray[i]));
       // a more appropriate one
        System.out.print("(" + (byteArray[i] & 0xFF) + ") ");
    }
    System.out.println("");
    System.out.println(new String(byteArray, encoding));

【讨论】:

  • 这也意味着使用 UTF-8 也可以正常工作。
  • 我只想补充一点,问题不是溢出问题。只是使用的公式是错误的,简单明了。例如,byteArray[i] &lt; 0 ? byteArray[i] + 256 : byteArray[i] 也可以正常工作。
  • @sstan 你说得对,我们称它为由- 操作引起的“下溢”,因为byte b = -1; System.out.println("&gt;&gt; " + (-b)) 会产生&gt;&gt; 1 而不是&gt;&gt; -2
  • @sstan 不过,你是对的,问题不是下溢/上溢,使用的公式完全错误
  • 谢谢大家 - 非常感谢您的帮助。这是我最喜欢的错误方式。
【解决方案2】:

我的猜测。

UTF-16LE 表示字符占用 2 或 4 个字节。

检查this 并向下滚动到 3/4。您将看到 190 和 194 (11000010 10111110) - 这是您对符号进行编码所需的两个字节,显然称为“VULGAR FRACTION THREE QUARTERS”。

当你创建byte[] 时,数组只能存储 1 个字节,不能存储两个,所以你会错过一个。看起来在 C# 中你错过了 194,而在 Java 中你错过了 190。

原因是字节序。见this答案。

在 Java 中,getBytes("UTF-16") 返回一个大端表示。

C# 的 System.Text.Encoding.Unicode.GetBytes 返回 little-endian 表示。

但是,在 Java 中,getBytes("UTF-16LE") 根据this 以小端序返回,这就是您正在使用的。

我现在有疑问。

我需要更多地考虑您在 Java 中到底在做什么。还不知道怎么解决。

【讨论】:

  • 感谢您的回复,@Pushkin。像您这样的担忧使我不再使用 UTF-8 到 UTF-16LE(以及 UTF-16BE)。 UTF-8 到 byte[] 的转换没有丢失任何字节 - 但是它们的顺序在 C# 和 Java 之间是不同的(我已经编辑了我的原始帖子来说明这一点)。
  • 查看这篇帖子 (stackoverflow.com/a/9438470/4868078) 了解我开始使用 UTF-16LE 的原因。
  • UTF-16 总是以 16 位块为单位 - 每个字符一个或两个块。 From Wikipedia: "编码是可变长度的,因为代码点是用一个或两个 16 位代码单元编码的。"
猜你喜欢
  • 1970-01-01
  • 2011-08-18
  • 2012-03-13
  • 2023-03-06
  • 2012-01-07
  • 2012-11-07
  • 2017-01-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多