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

在Java中生成3字节(0x800到0xffff)UTF-8编码,java,unicode,utf-8,Java,Unicode,Utf 8,我正在尝试生成一个随机的Unicode字符字符串。我想指定每个字符占用的字节数(1-4字节,因为我希望最终将它们转换为UTF-8字节数组),以及字符数。例如,如果我指定10作为字符串中的字符数,指定3作为每个字符的字节数,那么当我调用 str.getBytes(StandardCharsets.UTF_8).length 我应该得到30个字节 我的代码使用1、2和4字节为字符生成正确的字符串。但是,对于从0x800到0xffff的代码点,当我对返回的字符串调用getBytes时,每次得到的字

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

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();
}
private String generateRandomString(int numberOfCharacters,int bytesPerCharacter){
int启动;
内端;
开关(字节或字符){
案例1:
开始=0;
end=0x7f;
打破
案例2:
开始=0x80;
end=0x7ff;
打破
案例3:
开始=0x800;
end=0xffff;
打破
案例4:
开始=0x10000;
end=0x10ffff;
打破
违约:
抛出新ArgumentException(“每个字符字节的值无效”);
}
StringBuilder=新的StringBuilder(numberOfCharacters);
整数计数=0;
int范围=结束-开始;
for(int i=0;i
非常有趣的问题

TL;博士 答案是,一些生成的代码点不是有效的Unicode,Java知道这一点,当编码为UTF-8时,用
替换它们,这会减少计数,因为这些代码点只输出一个字节,而不是三个字节

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

  • 代码点
  • 代码点
结论
您不能生成随机整数值并期望所有数值都是有效的Unicode。编码包含此类代码点的
字符串将把无效代码点编码为
0x3f
“?”
)。

我似乎无法重现您遇到的问题。这是不确定的-如果您尝试使用大量字符(超过200个),我通常可以重现它。在U+0800-U+FFFF代码点范围内,注意U+D800-U+DFFF,它们是保留的,不应该出现在Unicode字符串中。还有U+E000-U+F8FF,这是私人使用的代码点。一旦超过U+FFFF,就会有很多私人使用/未分配的代码点。在任何情况下,您都可以跳过使用
getBytes(UTF8)
并手动编码代码点,即使它们不是“合法的”。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();
}
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