going through Elixir's handling of unicode:
iex> String.codepoints("abc§")
["a", "b", "c", "§"]
very good, and byte_size/2 of this is not 4 but 5, because the last char is taking 2 bytes, I get that.
The ? operator (or is it a macro? can't find the answer) tells me that
iex(69)> ?§
167
Great; so then I look into the UTF-8 encoding table, and see value c2 a7 as hex encoding for the char. That means the two bytes (as witnessed by byte_size/1) are c2 (94 in decimal) and a7 (167 in decimal). That 167 is the result I got when evaluating ?§ earlier. What I don't understand, exactly, is.. why that number is a "code point", as per the description of the ? operator. When I try to work backwards, and evaluate the binary, I get what I want:
iex(72)> <<0xc2, 0xa7>>
"§"
And to make me go completely bananas, this is what I get in Erlang shell:
24> <<167>>.
<<"§">>
25> <<"\x{a7}">>.
<<"§">>
26> <<"\x{c2}\x{a7}">>.
<<"§"/utf8>>
27> <<"\x{c2a7}">>.
<<"§">>
!! while Elixir is only happy with the code above... what is it that I don't understand? Why is Erlang perfectly happy with a single byte, given that Elixir insists that char takes 2 bytes - and Unicode table seems to agree?
答案 0 :(得分:5)
代码点是分配给角色的编号。它是一个抽象值,不依赖于某个地方实际内存中的任何特定表示。
为了存储字符,您必须将代码点转换为某个字节序列。有几种不同的方法可以做到这一点;每个称为Unicode转换格式,并命名为UTF- n ,其中 n 是基本编码单元的位数。曾经使用过UTF-7,假设使用7位ASCII,甚至无法可靠地传输一个字节的第8位;在现代系统中,有UTF-8,UTF-16和UTF-32。
由于最大代码点值适合21位,UTF-32是最简单的;您只需将代码点存储为32位整数。 (理论上可以使用UTF-24甚至是UTF-21,但是常见的计算平台自然会处理占据16位或16位倍数的值,并且必须更加努力地处理其他任何事情。)< / p>
所以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个代码点,情况并非如此。
答案 1 :(得分:4)
代码点是标识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
最后一个非常有趣并且可能令人困惑。在Erlang二进制文件中,如果传递的值大于255,则会被截断。因此,最后一个示例实际上是<<49831>>
,当截断变为<<167>>
时,它再次等同于latin1中的<<"§">>
。