在Java中生成3字节(0x800到0xffff)的UTF-8编码

时间:2017-12-13 00:09:18

标签: java unicode utf-8

我正在尝试生成随机的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();
}

1 个答案:

答案 0 :(得分:1)

非常有趣的问题

TL; DR

答案是,某些生成的代码点不是有效的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和0x3f = ?的十六进制转储中只有18行。在第8和第19个位置查找生成的“代码点”显示这些是无效的Unicode代码点。

结论

您无法生成随机整数值,并希望它们都是有效的Unicode。对包含此类代码点的String进行编码会将无效代码点编码为0x3f'?')。