Unicode UTF8、代码点及其在Erlang和Elixir中的表示

Unicode UTF8、代码点及其在Erlang和Elixir中的表示,unicode,erlang,elixir,Unicode,Erlang,Elixir,通过Elixir对unicode的处理: iex> String.codepoints("abc§") ["a", "b", "c", "§"] 很好,字节大小/2不是4而是5,因为最后一个字符占用了2个字节,我得到了 那个?运算符还是宏?找不到答案告诉我 iex(69)> ?§ 167 伟大的;然后我查看UTF-8编码表,看到值c2 a7是字符的十六进制编码。这意味着字节_size/1见证的两个字节是十进制的c2 94和十进制的a7 167。这就是我之前评估时得到的结果。确切地

通过Elixir对unicode的处理:

iex> String.codepoints("abc§")
["a", "b", "c", "§"]
很好,字节大小/2不是4而是5,因为最后一个字符占用了2个字节,我得到了

那个?运算符还是宏?找不到答案告诉我

iex(69)> ?§
167
伟大的;然后我查看UTF-8编码表,看到值c2 a7是字符的十六进制编码。这意味着字节_size/1见证的两个字节是十进制的c2 94和十进制的a7 167。这就是我之前评估时得到的结果。确切地说,我不明白的是。。为什么这个数字是一个代码点,根据的描述?操作人员当我尝试反向工作并计算二进制文件时,我得到了我想要的:

iex(72)> <<0xc2, 0xa7>>
"§"
为了让我彻底发疯,我在Erlang shell中得到了以下内容:

24> <<167>>.
<<"§">>
25> <<"\x{a7}">>.
<<"§">>
26> <<"\x{c2}\x{a7}">>.
<<"§"/utf8>>
27> <<"\x{c2a7}">>.    
<<"§">>

!!虽然Elixir只对上面的代码感到满意。。。我不明白的是什么?既然Elixir坚持字符需要2个字节,而Unicode表似乎也同意这一点,为什么Erlang对单字节非常满意呢?

代码点是识别Unicode字符的关键。§的代码点为167 0xA7。根据您选择的编码方式,代码点可以以不同的方式以字节表示

这里的混淆源于这样一个事实:编码为UTF-8时,代码点167 0xA7由字节0xC2 0xA7标识

当您将Erlang添加到对话中时,您必须记住Erlang默认编码是latin1,需要迁移到UTF-8,但我不确定它是否是在shell中实现的-请有人纠正我


在拉丁语1中,代码点§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的整数,它将被截断。因此,最后一个示例有效地实现了当截断时变为的功能,这与拉丁文1中的代码点相同。

代码点是标识Unicode字符的代码点。§的代码点为167 0xA7。根据您选择的编码方式,代码点可以以不同的方式以字节表示

这里的混淆源于这样一个事实:编码为UTF-8时,代码点167 0xA7由字节0xC2 0xA7标识

当您将Erlang添加到对话中时,您必须记住Erlang默认编码是latin1,需要迁移到UTF-8,但我不确定它是否是在shell中实现的-请有人纠正我


在拉丁语1中,代码点§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的整数,它将被截断。因此,最后一个示例有效地实现了在截断时变为的功能,这同样相当于拉丁语1。

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

为了存储字符,必须将代码点转换为某些字节序列。有几种不同的方法可以做到这一点;每种格式都称为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范围的字符都要求每个字符超过一个字节,即使是在 e范围从U+0080到U+00FF,十进制128到255,在过去是Unicode之前的默认拉丁-1编码中只占用一个字节

因此,对于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将保存代码点。因此,代码点的二进制表示形式10100111被填充到0001010100111,并被拆分为序列00010,该序列替换UTF-8模板中的abcde,以及100111,该序列替换fghijk。这将产生两个二进制值为11000010和10100111的字节,它们是十六进制的C2和A7,或十进制的194和167


您会注意到,第二个字节恰好与您正在编码的代码点具有相同的值,但重要的是要认识到,这种对应只是一种巧合。从128 U+0080到191 U+00BF,总共有64个代码点是这样工作的:它们的UTF-8编码由一个十进制值为194的字节和一个值等于代码点本身的字节组成。但对于Unicode中可能的其他1114048代码点,情况并非如此。

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

为了存储字符,必须将代码点转换为某些字节序列。有几种不同的方法可以做到这一点;每种格式都称为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范围内的字符,在Unicode之前的默认拉丁-1编码中,这些字符只占用一个字节

因此,对于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将保存代码点。因此,代码点的二进制表示形式10100111被填充到0001010100111,并被拆分为序列00010,该序列替换UTF-8模板中的abcde,以及100111,该序列替换fghijk。这将产生两个二进制值为11000010和10100111的字节,它们是十六进制的C2和A7,或十进制的194和167


您会注意到,第二个字节恰好与您正在编码的代码点具有相同的值,但重要的是要认识到,这种对应只是一种巧合。从128 U+0080到191 U+00BF,总共有64个代码点是这样工作的:它们的UTF-8编码由一个十进制值为194的字节和一个值等于代码点本身的字节组成。但对于Unicode中可能的其他1114048代码点,情况并非如此。

看起来Erlang将二进制字节打印为拉丁1编码字符,而Elixir将其视为ASCII。要了解Erlang shell中发生的情况,您需要知道使用的是哪个版本的Erlang,但任何最新版本都应该使用Unicode,您的语言环境设置是LANG、LC_*以及终端设置使用的编码。从您的示例中,看起来您使用的是非unicode区域设置,终端打印的是latin-1。看起来Erlang将二进制字节打印为latin1编码字符,而Elixir将其视为ASCII。要了解Erlang shell中发生的情况,您需要知道您使用的是哪个版本的Erlang,但最近的任何版本都应该使用Unicode,您的语言环境设置是LANG、LC_*以及您的终端设置使用的编码。从您的示例来看,您使用的似乎是非unicode语言环境,终端打印的是latin-1。这就是\x{…}被弃用的原因。使用\xHH表示单字节值,使用\uhhh表示Unicode代码点。=>슧在拉丁语1中,代码点§0xA7也由字节0xA7表示。事实上,这是我没有费心去检查的东西,我的错!当然,关于截断的部分真的完成了整个画面,谢谢!这就是为什么不推荐使用\x{…}。使用\xHH表示单字节值,使用\uhhh表示Unicode代码点。=>슧在拉丁语1中,代码点§0xA7也由字节0xA7表示。事实上,这是我没有费心去检查的东西,我的错!当然,关于截断的部分真的完成了整个画面,谢谢!虽然我已经接受了答案,但也谢谢你。我看的其中一页将十六进制值两个字节称为“code”,我将其解释为“codepoint”,这让我很困惑。同样,我也没有意识到这种巧合。谢谢你,尽管我已经接受了答案。我看的其中一页将十六进制值两个字节称为“code”,我将其解释为“codepoint”,这让我大惑不解。同样,我也确实没有意识到这种巧合。