UTF8,代码点及其在Erlang和Elixir中的表示形式

ale*_*pov 1 unicode erlang elixir

通过Elixir的unicode处理:

iex> String.codepoints("abc§")
["a", "b", "c", "§"]
Run Code Online (Sandbox Code Playgroud)

非常好,其中的byte_size / 2不是4而是5,因为最后一个char占用2个字节,我明白了。

?运算符(或者是宏?找不到答案)告诉我

iex(69)> ?§
167
Run Code Online (Sandbox Code Playgroud)

大; 因此,我查看了UTF-8编码表,并将值c2 a7视为char的十六进制编码。这意味着两个字节(由byte_size / 1见证)是c2(十进制为94)和a7(十进制为167)。167是我之前评估?§时得到的结果。确切地说,我不明白的是..为什么根据“ ”的描述,该数字是“代码点” 操作员。当我尝试向后工作并评估二进制文件时,我得到了想要的:

iex(72)> <<0xc2, 0xa7>>
"§"
Run Code Online (Sandbox Code Playgroud)

为了使我完全香蕉,这就是我在Erlang shell中得到的:

24> <<167>>.
<<"§">>
25> <<"\x{a7}">>.
<<"§">>
26> <<"\x{c2}\x{a7}">>.
<<"§"/utf8>>
27> <<"\x{c2a7}">>.    
<<"§">>
Run Code Online (Sandbox Code Playgroud)

!! 而Elixir只对上面的代码感到满意...我不了解什么?考虑到Elixir坚持char占用2个字节,而Unicode表似乎同意,为什么Erlang对单个字节完全满意?

Jos*_*lim 8

代码点用于标识 Unicode 字符。§ 的代码点是 167 (0xA7)。代码点可以以不同的方式以字节表示,具体取决于您选择的编码。

这里的混淆来自这样一个事实,即编码为 UTF-8 时,代码点 167 (0xA7) 由字节 0xC2 0xA7 标识。

当您将 Erlang 添加到对话中时,您必须记住 Erlang 默认编码是/是 latin1(有人努力迁移到 UTF-8,但我不确定它是否适用于 shell - 请有人纠正我)。

在 latin1 中,代码点 § (0xA7) 也由字节 0xA7 表示。所以直接解释你的结果:

24> <<167>>.
<<"§">> %% this is encoded in latin1

25> <<"\x{a7}">>.
<<"§">> %% still latin1

26> <<"\x{c2}\x{a7}">>.
<<"§"/utf8>> %% this is encoded in utf8, as the /utf8 modifier says

27> <<"\x{c2a7}">>.
<<"§">>  %% this is latin1
Run Code Online (Sandbox Code Playgroud)

最后一个非常有趣,而且可能令人困惑。在 Erlang 二进制文件中,如果你传递一个大于 255 的整数,它会被截断。所以最后一个例子是有效地执行<<49831>>which 当被截断时变为<<167>>,这又相当于<<"§">>在 latin1 中。


Mar*_*eed 5

代码点是分配给字符的数字。它是一个抽象值,不依赖于实际内存中某处的任何特定表示形式。

为了存储字符,您必须将代码点转换为某些字节序列。有几种不同的方法可以做到这一点。每种都称为Unicode转换格式,并命名为UTF- n,其中n是编码的基本单位是多少位。以前曾经使用过UTF-7,其中假设使用7位ASCII,甚至不能可靠地传输字节的第8位。在现代系统中,有UTF-8,UTF-16和UTF-32。

由于最大的代码点值适合21位,因此UTF-32最简单;您只需将代码点存储为32位整数。(理论上可能会有UTF-24甚至是UTF-21,但是常见的计算平台自然会处理占用8位或16位整数的值,并且必须更加努力地处理其他任何值。)

因此,UTF-32很简单,但是效率很低。它不仅具有11个永远不需要的额外位,而且具有5个几乎不再需要的位。在野外发现的大多数Unicode字符几乎都在基本多语言平面中,从U + 0000到U + FFFF。UTF-16使您可以将所有这些代码点表示为纯整数,占用UTF-32的一半空间。但是它不能代表U + 10000的任何内容,因此,将0000-FFFF范围的一部分保留为“代理对”,它们可以放在一起代表具有两个16位单元的高平面Unicode字符,总共需要32位,但仅在需要时才使用。

Java内部使用UTF-16,但是Erlang(以及Elixir)以及大多数其他编程系统都使用UTF-8。UTF-8具有与ASCII完全透明兼容的优势-ASCII范围内的所有字符(U + 0000至U + 007F或0-127十进制)都由具有相应值的单个字节表示。但是,任何代码点超出ASCII范围的字符都需要一个以上的字节-甚至那些范围在U + 0080至U + 00FF,十进制128至255之间的字符,它们在Latin-1编码中仅占用了一个字节,是Unicode之前的默认值。

因此,对于Elixir / Erlang“二进制文件”,除非您竭尽全力地对事物进行不同的编码,否则您将使用UTF-8。如果查看UTF-8字符的第一个字节的高位,则为0,表示您具有一个字节的ASCII字符,或者为1。如果为1,则第二高位也为1。因为在到达0位之前从高位开始递减的连续1位的数目告诉您字符占用了多少字节。因此,模式110xxxxx表示字符是两个字节,1110xxxx表示三个字节,11110xxx表示四个字节。(尽管编码理论上最多可以支持七个字节,但没有合法的UTF-8字符需要四个以上的字节。)

其余字节的两个高位均设置为10,因此不会把它们误认为是字符的开头。其余的位是代码点本身。

以您的情况为例,“§”的代码点是U + 00A7-即十六进制A7,它是十进制167或二进制10100111。由于它大于十进制127,因此它将需要UTF-8中的两个字节。这两个字节将采用二进制形式110abcde 10fghijk,其中的位abcdefghijk将保存代码点,前导0将其填充为11位。这将为您提供11000010 10100111,十六进制c2 a7或十进制194,后跟167。

您会注意到第二个字节恰好与您要编码的代码点具有相同的值;重要的是要意识到这种对应只是一个巧合。总共有64个代码点可以这样解决:它们的UTF-8编码的第一个字节为十进制194,第二个字节等于实际的代码点。但是对于Unicode中可能的其他1,114,048个代码点,情况并非如此。