如何强制 Java 对 Unicode 字符(例如 'ł')每个字符仅使用 2 个字节?

How to force Java to use only 2 bytes per character for Unicode characters (eg. 'ł')?

我正在努力将哈希算法从 C# 转换为 Java,它需要使用字符串的字节数组。问题是在处理像 'ł' 和 'ą' 这样的字符时,Java 将这些字母转换为 2 个字符,从而给我 4 个字节而不是我期望的 2 个字节。

我尝试使用 string.codePointAt() 而不是 string.charAt(),但它继续将这些字母处理为 2 个字符而不是 1 个。我认为 Java 使用与 C# 相同的 16 位 Unicode & VB 但是当 C# 和 VB 能够将这些字母转换为 2 个字节时,为什么这个字母需要 4 个字节。

C# 和 VB 将 'ł' 的字节读取为:[66, 1](下面的代码)

 bytes = Encoding.Unicode.GetBytes("ł");
 Console.WriteLine(string.Join(",", bytes));

Java 将 'ł' 的字节读取为:[-59, 0, 26, 32](下面的代码)

String str = "ł";
byte[] B = str.getBytes(Charset.forName("UTF-16LE"));
System.out.println(Arrays.toString(B));

我什至也尝试过使用 StandardCharsets,但仍然是同样的问题。

有没有办法让 Java 将这些字母作为单个 UTF-16 字符处理,而不是将其分成 2 个字符?

PS:我也不能重构算法,因为它已经在使用了,而且它也必须在我们的新 Java 中完成。

PPS:我尝试规范化字符串,但仍然存在差异,当 C# 为字符 [=15 输出 [230,0] 时,字符“æ”用 [-26,0] 读取=]

我发现了问题,正如 Ralf Kleberhoff 所猜测的那样,我没有使用 Java 编译器期望的正确文件编码。我的文件使用的是 UTF-16LE 编码,所以我在编译文件时只传递了 -encoding "UTF-16"。

javac -encoding "UTF-16" HashBrowns.java

此外,正如 Samuel Hunter 所建议的,我将这些值转换为正数以确保我得到的值与我在 C# 和 VB6 中得到的值完全相同。

private int[] convertSignedBytesToUnsignedint(byte[] b)
{
    int[] intArr = new int[b.length];
    for (int i = 0; i < b.length; i++) {
        intArr[i] = b[i] & 0xff;
    }
    return intArr;
}

我不确定哪个代码更优化,但我只是想 post 在这里,这样我就可以分享适合我的情况的代码。

虽然 Java 的内部字符编码是 UTF-16BE,但 String#codePointAt(int)String#getBytes()(没有提供参数)都使用默认字符编码,这取决于 Java 实现及其所在的平台。您使用 String.getBytes(Charset.forName("UTF-16LE")) 的想法是正确的,但我建议您改用 String.getBytes(StandardCharsets.UTF_16LE)

C# 返回 [230,0] 而 Java returns [-26, 0] 的第二个问题:从技术上讲,它们在位方面是相同的。但是,C# 的字节数组保存无符号字节,而 Java 的数组保存有符号字节。即使 Java 和 C# 都提供相同的字节模式,如果您 真的 想要表达一个正值,您可以将它们存储在 short 数组中:

        String str = "æ";

        byte[] byteArray = "æ".getBytes(StandardCharsets.UTF_16LE);
        short[] newByteArray = new short[byteArray.length];

        for (int i = 0; i < byteArray.length; i++) {
            byte c = byteArray[i];
            newByteArray[i] = (c >= 0) ? c : (short)(c + 256);
        }

        System.out.println(Arrays.toString(byteArray));
        // => [-26, 0]
        System.out.println(Arrays.toString(newByteArray));
        // => [230, 0]

FWIW,用 ł 替换 æ 给我 [66, 1] 字节数组和短数组。

尽管代码将数组“转换”为无符号“字节”,如果可以的话我建议不要这样做,因为字节数组给出与 C# 相同的模式,并承诺相同的数字大小。