第一个是字节串:
>>> "\xF0\x9F\x8C\x80".decode('utf8')
u'\U0001f300'
u"\ud83c\udf00" 一个是 UTF16 版本(四位 unicode 转义)
u"\U0001F300" 是代码点的实际索引。
但是这些数字有什么关系呢?这是个难题。它是由编码定义的,没有明显的关系。为了给你一个想法,这里是一个“手动”将索引 0x1F300 处的代码点编码为 UTF-8 的示例:
旋风字符 ? 的索引为 0x1f300,位于 0x00010000 - 0x001FFFFF 范围内。这个范围的模板是:
11110... 10...... 10...... 10......
用代码点的二进制表示填充点。我不能告诉你为什么模板看起来像那样,它只是 utf-8 定义。
这是我们代码点的二进制表示:
>>> u'?'
u'\U0001f300'
>>> unichr(0x1f300)
u'\U0001f300'
>>> bin(0x1f300)
'0b11111001100000000'
因此,如果我们采用字符串模板并像这样填充它(使用一些前导零,因为模板中的插槽比我们的数字中的有效数字多),我们会得到:
11110... 10...... 10...... 10......
11110000 10011111 10001100 10000000
现在让我们将其转换回十六进制
>>> 0b11110000100111111000110010000000
4036988032
>>> hex(4036988032)
'0xf09f8c80'
然后你就有了代码点的 UTF8 表示。
对于 UTF16,您的代码点有一个 similar magic recipe:从索引中减去 0x10000,然后我们用零填充以获得 20 位二进制表示。前十位添加到 0xD800 以给出第一个 16 位代码单元。最后十位添加到 0xDC00 以提供第二个 16 位代码单元。
>>> bin(0x1f300 - 0x10000)[2:].rjust(20, '0')
'00001111001100000000'
>>> _[:10], _[10:]
('0000111100', '1100000000')
>>> hex(0b0000111100 + 0xd800)
'0xd83c'
>>> hex(0b1100000000 + 0xdc00)
'0xdf00'
还有您的 UTF 16 版本,即带有小写 \u 转义的版本。
正如您可能理解的那样,这些表示中的十六进制数字之间可能没有明显的数字关系,它们只是同一代码点的不同编码。