在修改一个cms的过程当中遇到一个php截取字符串的函数(当然得兼容中英字符了),因为对各种编码的字符范围和字符表示不清楚,感觉一头迷雾,虽然可以直接来调用这个函数
但是我这个的特点是追究原理,我在乎的事情都想弄明白,于是各个qq群依次发信息,没人理会。唉,郁闷。只好自己google it and teach myself 。下面是详细介绍。
还有对各方求助没有人理会,我有些个人想法。现在的人已经很少有人去深究理论了,人们的观念是得过且过,人们通常只是知道什么,不知道为什么。对编程来说,个人认为这是很悲哀的事情,也是非常危险的事情。我想可能这也是中国的IT落后于美国的原因,我希望中国的编程人员能够好好想想了。
下面的东西是从网上查到的
Unicode 的编码和实现
大概来说,Unicode 编码系统可分为编码方式和实现方式两个层次。
编码方式
Unicode 的编码方式与 ISO 10646 的通用字符集(Universal Character Set,UCS)概念相对应,目前实际应用的 Unicode 版本对应于 UCS-2,使用16位的编码空间。也就是每个字符占用2个字节。这样理论上一共最多可以表示 216 即 65536 个字符。基本满足各种语言的使用。实际上目前版本的 Unicode 尚未填充满这16位编码,保留了大量空间作为特殊使用或将来扩展。
上述16位 Unicode 字符构成基本多文种平面(Basic Multilingual Plane,简称 BMP)。最新(但未实际广泛使用)的 Unicode 版本定义了16个辅助平面,两者合起来至少需要占据21位的编码空间,比3字节略少。但事实上辅助平面字符仍然占用4字节编码空间,与 UCS-4 保持一致。未来版本会扩充到 ISO 10646-1 实现级别3,即涵盖 UCS-4 的所有字符。UCS-4 是一个更大的尚未填充完全的31位字符集,加上恒为0的首位,共需占据32位,即4字节。理论上最多能表示 231 个字符,完全可以涵盖一切语言所用的符号。
BMP 字符的 Unicode 编码表示为 U+hhhh,其中每个 h 代表一个十六进制数位。与 UCS-2 编码完全相同。对应的4字节 UCS-4 编码后两个字节一致,前两个字节的所有位均为0。
关于 Unicode 和 ISO 10646 及 UCS 的详细关系 ,请参看通用字符集。
实现方式
Unicode 的实现方式不同于编码方式。一个字符的 Unicode 编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对 Unicode 编码的实现方式有所不同。Unicode 的实现方式称为Unicode转换格式(Unicode Translation Format,简称为 UTF)。
例如,如果一个仅包含基本7位ASCII字符的 Unicode 文件,如果每个字符都使用2字节的原 Unicode 编码传输,其第一字节的8位始终为0。这就造成了比较大的浪费。对于这种情况,可以使用 UTF-8 编码,这是一种变长编码,它将基本7位ASCII字符仍用7位编码表示,占用一个字节(首位补0)。而遇到与其他 Unicode 字符混合的情况,将按一定算法转换,每个字符使用1-3个字节编码,并利用首位为0或1进行识别。这样对以7位ASCII字符为主的西文文档就大大节省了编码长度(具体方案参见UTF-8)。类似的,对未来会出现的需要4个字节的辅助平面字符和其他 UCS-4 扩充字符,2字节编码的 UTF-16 也需要通过一定的算法进行转换。
再如,如果直接使用与 Unicode 编码一致(仅限于 BMP 字符)的 UTF-16 编码,由于每个字符占用了两个字节,在Macintosh (Mac)机和PC机上,对字节顺序的理解是不一致的。这时同一字节流可能会被解释为不同内容,如某字符为十六进制编码4E59,按两个字节拆分为4E和59,在Mac上读取时是从低字节开始,那么在Mac OS会认为此4E59编码为594E,找到的字符为“奎”,而在Windows上从高字节开始读取,则编码为 U+4E59 的字符为“乙”。就是说在Windows下以UTF-16编码保存一个字符“乙”,在Mac OS里打开会显示成“奎”。此类情况说明UTF-16的编码顺序若不加以人为定义就可能发生混淆,于是在 UTF-16 编码实现方式中使用了大尾序(Big-Endian, 简写为UTF-16 BE)、小尾序(Little-Endian, 简写为UTF-16 LE)的概念,以及可附加的BOM(Byte Order Mark)解决方案,目前在PC机上的Windows系统和Linux系统对于UTF-16编码默认使用UTF-16 LE。(具体方案参见UTF-16)
此外 Unicode 的实现方式还包括 UTF-7、Punycode、CESU-8、SCSU、UTF-32等,这些实现方式有些仅在一定的国家和地区使用,有些则属于未来的规划方式。目前通用的实现方式是 UTF-16小尾序(BOM)、UTF-16大尾序(BOM)和 UTF-8。在微软公司Windows XP操作系统附带的记事本(Notepad)中,“另存为”对话框可以选择的四种编码方式除去非 Unicode 编码的ANSI(对于英文系统即ASCII编码,中文系统则为GB2312或Big5编码) 外,其余三种为“Unicode”(对应UTF-16 LE)、“Unicode big endian”(对应UTF-16 BE)和“UTF-8”。
目前辅助平面的工作主要集中在第二和第三平面的中日韩统一表意文字中,因此包括GBK、GB18030、Big5等简体中文、繁体中文、日文、韩文以及越南喃字的各种编码与 Unicode 的协调性被重点关注。考虑到 Unicode 最终要涵盖所有的字符,从某种意义而言,这些编码方式也可视作 Unicode 的出现于其之前的既成事实的实现方式,如同ASCII及其扩展Latin-1一样,后两者的字符在16位 Unicode 编码空间中的编码第一字节各位全为0,第二字节编码与原编码完全一致。但上述东亚语言编码与 Unicode 编码的对应关系要复杂得多。
utf-8
UTF-8(8 位元 Universal Character Set/Unicode Transformation Format)是一种针对 Unicode 的可变长度字符编码。它可以用来表示 Unicode 标准中的任何字符,且其编码中的第一个字节仍与 ASCII 相容,这使得原来处理 ASCII 字符的软件无须或只须做少部份修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他储存或传送文字的应用中,优先采用的编码。
UTF-8 使用一至四个字节为每个字符编码:
- 128 个 US-ASCII 字符只需一个字节编码(Unicode 范围由 U+0000 至 U+007F)。
- 带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要二个字节编码(Unicode 范围由 U+0080 至 U+07FF)。
- 其他基本多文种平面(BMP)中的字符(这包含了大部分常用字)使用三个字节编码。
- 其他极少使用的 Unicode 辅助平面的字符使用四字节编码。
对上述提及的第四种字符而言,UTF-8 使用四个字节来编码似乎太耗费资源了。但 UTF-8 对所有常用的字符都可以用三个字节表示,而且它的另一种选择,UTF-16编码,对前述的第四种字符同样需要四个字节来编码,所以要决定 UTF-8 或 UTF-16 哪种编码比较有效率,还要视所使用的字符的分布范围而定。不过,如果使用一些传统的压缩系统,比如 DEFLATE,则这些不同编码系统间的的差异就变得微不足道了。若顾及传统压缩算法在压缩较短文字上的效果不大,可以考虑使用 Standard Compression Scheme for Unicode(SCSU)。
Unicode字符的位元被分割为数个部分,并分配到UTF-8的字节串中较低的位元的位置。在U+0080的以下字符都使用内含其字符的单字节编码。这些编码正好对应7位元的ASCII字符。在其他情况,有可能需要多达4个字符组来表示一个字符。这些多字节的最高有效位元会设定成1,以防止与7位元的ASCII字符混淆,并保持标准的字节主导字串(standard byte-oriented string)运作顺利。
代码范围 十六进制 |
标量值(scalar value) 二进制 |
UTF-8 二进制/十六进制 |
注释 |
000000 - 00007F 128个代码 |
00000000 00000000 0zzzzzzz |
0zzzzzzz(00-7F) |
ASCII字符范围,字节由零开始 |
七个z |
七个z |
000080 - 0007FF 1920个代码 |
00000000 00000yyy yyzzzzzz |
110yyyyy(C2-DF) 10zzzzzz(80-BF) |
第一个字节由110开始,接着的字节由10开始 |
三个y;二个y;六个z |
五个y;六个z |
000800 - 00D7FF 00E000 - 00FFFF 61440个代码 [Note 1] |
00000000 xxxxyyyy yyzzzzzz |
1110xxxx(E0-EF) 10yyyyyy 10zzzzzz |
第一个字节由1110开始,接着的字节由10开始 |
四个x;四个y;二个y;六个z |
四个x;六个y;六个z |
010000 - 10FFFF 1048576个代码 |
000wwwxx xxxxyyyy yyzzzzzz |
11110www(F0-F4) 10xxxxxx 10yyyyyy 10zzzzzz |
由11110开始,接着的字节由10开始 |
三个w;二个x;四个x;四个y;二个y;六个z |
三个w;六个x;六个y;六个z |
- Note 1 Unicode在范围D800-DFFF中不存在任何字符,基本多文种平面中约定了这个范围用于UTF-16扩展标识辅助平面(两个UTF-16表示一个辅助平面字符)。当然,任何编码都是可以被转换到这个范围,但在unicode中他们并不代表任何合法的值。
以上这个表是php截取utf-8字符串的关键,根据每个字节的前几位的数字决定该字符占几个字节(utf-8的编码有点类似于5类IP地址的编码)
例如,希伯来语字母 aleph(א)的Unicode代码是 U+05D0,按照以下方法改成 UTF-8:
- 它属于 U+0080到U+07FF区域,这个表说明它使用双字节,110yyyyy 10zzzzzz.
- 十六进制 的 0x05D0换算成二进制就是 101-1101-0000.
- 这11位数按顺序放入"y"部分和"z"部分:11010111 10010000.
- 最后结果就是双字节,用十六进制写起来就是 0xD7 0x90,这就是这个字符aleph(א)的UTF-8编码。
所以开始的128个字符(US-ASCII)只需一字节,接下来的1920个字符需要双字节编码,包括带附加符号的拉丁字母,希腊字母,西里尔字母,科普特语字母,亚美尼亚语字母,希伯来文字母和阿拉伯字母的字符。基本多文种平面中其余的字符使用三个字节,剩余字符使用四个字节。
设计UTF-8的理由(utf-8的特点)
UTF-8的设计有以下的多字符组序列的特质:
- 单字节字符的最高有效位元永远为0。
- 多字节序列中的首个字符组的几个最高有效位元决定了序列的长度。最高有效位为
110 的是2字节序列,而1110 的是三字节序列,如此类推。
- 多字节序列中其余的字节中的首两个最高有效位元为
10 。
UTF-8的这些特质,保证了一个字符的字节序列不会包含在另一个字符的字节序列中。这确保了以字节为基础的部份字串比对(sub-string match)方法可以适用于在文字中搜寻字或词。有些比较旧的可变长度8位元编码(如Shift JIS)没有这个特质,故字串比对的算法变得相当复杂。虽然这增加了UTF-8编码的字串的信息冗余,但是利多于弊。另外,资料压缩并非Unicode 的目的,所以不可混为一谈。即使在传送过程中有部份字节因错误或干扰而完全遗失,还是有可能在下一个字符的起点重新同步,令受损范围受到限制。
另一方面,由于其字节序列设计,如果一个疑似为字符串的序列被验证为UTF-8编码,那么我们可以有把握地说它是UTF-8字符串。一段两字节随机序列碰巧为合法的UTF-8而非ASCII 的机率为32分1。对于三字节序列的机率为256分3,对更长的序列的机率就更低了。
-
- 在ASCII码的范围,用一个字节表示,超出ASCII码的范围就用字节表示,这就形成了我们上面看到的UTF-8的表示方法,这様的好处是当UNICODE文件中只有ASCII码时,储存的文件都为一个字节,所以就是普通的ASCII文件无异,读取的时候也是如此,所以能与以前的ASCII文件相容。
-
- 大于ASCII码的,就会由上面的第一字节的前几位表示该unicode字符的长度,比如110xxxxxx前三位的二进制表示告诉我们这是个 2BYTE的UNICODE字符;1110xxxx是个三位的UNICODE字符,依此类推;xxx 的位置由字符编码数的二进制表示的位填入。越靠右的 x 具有越少的特殊意义。只用最短的那个足够表达一个字符编码数的多字节串。注意在多字节串中,第一个字节的开头"1"的数目就是整个串中字节的数目。。
utf-8的特性
- UCS 字符 U+0000 到 U+007F (ASCII) 被编码为字节 0x00 到 0x7F(ASCII 兼容),这也意味着只包含 7 位 ASCII 字符的文件在 ASCII 和 UTF-8 两种编码方式下是一样的。
- 所有 >U+007F 的 UCS 字符被编码为一个多个字节的串,每个字节都有标记位集。因此,ASCII 字节 (0x00-0x7F) 不可能作为任何其他字符的一部分。
- 表示非 ASCII 字符的多字节串的第一个字节总是在 0xC0 到 0xFD 的范围里,并指出这个字符包含多少个字节。多字节串的其余字节都在 0x80 到 0xBF 范围里,这使得重新同步非常容易,并使编码无国界,且很少受丢失字节的影响。
- 可以编入所有可能的 231个 UCS 代码
- UTF-8 编码字符理论上可以最多到 6 个字节长,然而 16 位 BMP 字符最多只用到 3 字节长。
- Bigendian UCS-4 字节串的排列顺序是预定的。
- 字节 0xFE 和 0xFF 在 UTF-8 编码中从未用到,同时,UTF-8以字节为编码单元,它的字节顺序在所有系统中都是一様的,没有字节序的问题,也因此它实际上并不需要BOM。
- 与 UTF-16 或其他 Unicode 编码相比,对于不支援 Unicode 和 XML 的系统,UTF-8 更不容易造成问题。
gb18030
与GB 2312-1980完全兼容,与GBK基本兼容,支持GB 13000及Unicode的全部统一汉字,共收录汉字70244个。
GB 18030主要有以下特点:
- 采用多字节编码,每个字可以由1个、2个或4个字节组成。(变长编码)
- 编码空间庞大,最多可定义161万个字符。
- 支持中国国内少数民族的文字,不需要动用造字区。
字节结构
- 单字节,其值从0到0x7F。
- 双字节,第一个字节的值从0x81到0xFE,第二个字节的值从0x40到0xFE(不包括0x7F)。
- 四字节,第一个字节的值从0x81到0xFE,第二个字节的值从0x30到0x39,第三个字节从0x81到0xFE,第四个字节从0x30到0x39。
gb2312
GB2312编码通行于中国大陆;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB 2312。
GB 2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时,GB 2312收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。
GB 2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。
对于人名、古汉语等方面出现的罕用字,GB 2312不能处理,这导致了后来GBK及GB 18030汉字字符集的出现。
GB 2312中对所收汉字进行了“分区”处理,每区含有94个汉字/符号。这种表示方式也称为区位码。
- 01-09区为特殊符号。
- 16-55区为一级汉字,按拼音排序。
- 56-87区为二级汉字,按部首/笔画排序。
10-15区及88-94区则未有编码。
举例来说,“啊”字是GB2312之中的第一个汉字,它的区位码就是1601。
字节结构
在使用GB2312的程序中,通常采用EUC储存方法,以便兼容于ASCII。浏览器编码表上的“GB2312”,通常都是指“EUC-CN”表示法。
每个汉字及符号以两个字节来表示。第一个字节称为“高位字节”,第二个字节称为“低位字节”。
“高位字节”使用了0xA1-0xF7(把01-87区的区号加上0xA0),“低位字节”使用了0xA1-0xFE(把01-94加上0xA0)。 由于一级汉字从16区起始,汉字区的“高位字节”的范围是0xB0-0xF7,“低位字节”的范围是0xA1-0xFE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。
例如“啊”字在大多数程序中,会以两个字节,0xB0(第一个字节)0xA1(第二个字节)储存。(与区位码对比:0xB0=0xA0+16,0xA1=0xA0+1)。
EUC-CN(网上下到的编码表就是这个)(gb2312的字符串截取也是按照该表截取的)
EUC-CN是GB 2312最常用的表示方法。浏览器编码表上的“GB2312”,通常都是指“EUC-CN”表示法。
GB 2312字符使用两个字节来表示。
- “第一位字节”使用0xA1-0xF7
- “第二位字节”使用0xA1-0xFE
举例来说,“啊”字是GB 2312之中的第一个汉字,它的区位码是1601。
在EUC-CN之中,它把0xA0+16=0xB0,0xA0+1=0xA1,得出0xB0A1。 |