【问题标题】:Generating 3-byte (0x800 to 0xffff) UTF-8 encodings in Java在 Java 中生成 3 字节(0x800 到 0xffff)的 UTF-8 编码
【发布时间】:2017-12-13 00:09:18
【问题描述】:

我正在尝试生成一个随机的 Unicode 字符字符串。我想指定每个字符占用的字节数(1-4 个字节,因为我想最终将它们转换为 UTF-8 字节数组),以及字符数。例如,如果我将字符串中的字符数指定为 10,将每个字符的字节数指定为 3,我应该得到一个字符串 str,并且当我调用时

str.getBytes(StandardCharsets.UTF_8).length 

我应该得到 30 个字节。

我的代码使用 1、2 和 4 个字节为字符生成正确的字符串。但是,对于从 0x800 到 0xffff 的代码点,当我在返回的字符串上调用 getBytes 时,每次都会得到不同数量的字节。任何想法为什么会发生这种情况?

private String generateRandomString(int numberOfCharacters, int bytesPerCharacter) {

        int start;
        int end;

        switch (bytesPerCharacter) {
            case 1:
                start = 0;
                end = 0x7f;
                break;
            case 2:
                start = 0x80;
                end = 0x7ff;
                break;
            case 3:
                start = 0x800;
                end = 0xffff;
                break;
            case 4:
                start = 0x10000;
                end = 0x10ffff;
                break;
            default:
                throw new ArgumentException("Invalid value for the bytes per character");
        }
        StringBuilder builder = new StringBuilder(numberOfCharacters);
        int count = 0;
        int range = end - start;
        for (int i = 0; i < numberOfCharacters; i++) {
            builder.appendCodePoint((int) (Math.random() * range + start));
        }
        return builder.toString();
}

【问题讨论】:

  • 我似乎无法重现您遇到的问题。
  • 它是不确定的——如果你尝试使用大量的字符(超过 200 个),我通常可以重现它。

标签: java unicode utf-8


【解决方案1】:

非常有趣的问题

TL;博士

答案是某些生成的代码点不是有效的 Unicode,Java 知道这一点,在编码为 UTF-8 时将它们替换为?,这会导致计数错误,因为这些代码点只输出一个字节代码点,而不是三个。

说明

public static void main(String[] args) {
    int start = 0x800;
    int end   = 0xffff;
    int range = end-start;
    StringBuilder b = new StringBuilder();
    for (int i=0; i<20; i++)
    {
        int a = (int)(Math.random() * range + start);
        b.appendCodePoint(a);
        System.out.printf("Code point %5d length=%d\n", a, b.length());
    }
    byte[] result = b.toString().getBytes(StandardCharsets.UTF_8);
    System.out.println(result.length);
    for (byte x : result)
    {
        // newline before any byte matching 1110 xxxx (start of 3-byte UTF-8)
        if ((x & 0xF0) == 0xE0) System.out.println();
        System.out.printf("%02x ", x);
    }
    System.out.println();
}

在某些运行中,这会产生不到 60 个字节,例如这个:

Code point 35798 length=1
Code point 30523 length=2
Code point 43674 length=3
Code point  2743 length=4
Code point 64416 length=5
Code point  2438 length=6
Code point 15808 length=7
Code point 56254 length=8
Code point 20690 length=9
Code point 48789 length=10
Code point 52635 length=11
Code point  9128 length=12
Code point  8445 length=13
Code point 27765 length=14
Code point 63710 length=15
Code point 53350 length=16
Code point 41031 length=17
Code point 25939 length=18
Code point 56414 length=19
Code point 46327 length=20
56

e8 af 96 
e7 9c bb 
ea aa 9a 
e0 aa b7 
ef ae a0 
e0 a6 86 
e3 b7 80 3f 
e5 83 92 
eb ba 95 
ec b6 9b 
e2 8e a8 
e2 83 bd 
e6 b1 b5 
ef a3 9e 
ed 81 a6 
ea 81 87 
e6 95 93 3f 
eb 93 b7 

请注意,UTF-8 的十六进制转储中只有 18 行,0x3f = ?。在第 8 位和第 19 位查找生成的“代码点”会发现这些是无效的 Unicode 代码点。

结论

您不能生成随机整数值并期望它们都是有效的 Unicode。编码包含此类代码点的String 会将无效代码点编码为0x3f ('?')。

【讨论】:

  • 在 U+0800 - U+FFFF 范围的代码点中,请注意 U+D800 - U+DFFF,它们是保留的,不应出现在 Unicode 字符串中。还有 U+E000 - U+F8FF,它们是私人使用的代码点。一旦超过 U+FFFF,就会有很多私人使用/未分配的代码点。在任何情况下,您总是可以跳过使用getBytes(UTF8) 并手动编码代码点,即使它们不是“合法的”。 UTF-8 很容易通过句柄实现,它只关心编码位,不关心实际的码位值是什么,除了分组位。
猜你喜欢
  • 2011-11-28
  • 1970-01-01
  • 2014-03-17
  • 1970-01-01
  • 2010-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多