字符编码原理

Jackey 其他 481 次浏览 , , 没有评论

基本语法

PHP字符串有集中表达方式
  • 单引号
  • 双引号
  • heredoc 语法结构
  • nowdoc 语法结构(PHP5.3)
单引号与双引号的区别?
查看下面语句的输出结果:
  1. <?php
  2. echo "this is a pen, \nthis is a pen" . "<br>";
  3. echo 'this is a pen, \nthis is a pen' . "<br>";
  4. echo "\101\102" . "<br>";
  5. echo "\x41\x42" . "<br>";
  6. echo "\\" . "<br>";
  7. echo '\\' . "<br>";

双引号到底解析哪些字符:
  • 当字符串用双引号或者heredoc结构定义时,其中的变量将会被解析
  • \n,\r,\t,\v,\e,\f,\\,\$,\”
  • \[0-7]{1,3} 符合该正则表达式序列的是一个以八进制方式来表达的字符
  • \x[0-9A-Fa-f]{1,2} 符合该正则表达式序列的是一个以十六进制方式来表达的字符

字符串结构体

底层C语言中怎么表示PHP字符串?
  1. struct{
  2. char *val;
  3. int len;
  4. }str;

  • 字节组成的数组可以用[]或者{}访问某个字符
测试:
  1. <?php
  2. $str = '123456';
  3. echo $str[0];
  4. echo "<br>";
  5. echo $str[1];

  • 字符串长度可以达到2G内存
  • 常见函数都是单字节处理方式
  • 可以用[]或者{}访问单个字符
  • PHP字符串是二进制安全的

测试和小结

题目:
下面这段代码输出的结果是?
  1. <?php
  2. $str = 'a';
  3. $str[100] = 'e';
  4. echo ($str);

用超出字符串长度的下标写入将会拉长该字符串并以空格填充。

字符串存取总结

  • string中的字符可以通过一个从0开始的下标,用类似array结构中的方括号包含对应的数字来访问和修改,比如$str[42]。
  • 也可用花括号访问,比如$str{42}。数组可以这么访问么?(可以)
  • 用超出字符串长度的下标写入将会拉长该字符串并以空格填充。
  • 非整数类型下标会被转换成整数。非法下标类型会产生一个E_NOTICE级别错误。

字符串“串行化”

  • 大部分的PHP值可以转变成string来永久保存
  • 方法一:函数serialize()可以实现–数组和对象皆可以
  • 方法二:函数json_encode()可以实现–跨语言性较好
  • 方法三:函数var_export($items,true);
  1. <?php
  2. $arr = [1,2,3,4,5,6];
  3. var_export($arr);
  4.  
  5. $str = var_export($arr, true);
  6. echo $str;
  7.  
  8. $str = '<?php return ' . $str . ';';
  9. file_put_contents('xya', $str);
  10.  
  11. $arr2 = include 'xya';
  12. var_dump($arr2);

位、字节、字符

  • 位(bit b)指二进制中的一位,是二进制最小信息单位
  • 字节(Byte B)是计算机信息技术用于计量存储容量的一种计量单位。
  • 字符(Character)是指计算机中使用的字母、数字、汉字和符号等。

字符集和字符编码

  • 字符集(Charset):是一个系统支持的所有抽象字符的集合
  • 字符编码(Character Encoding):是一套法则,使字符集与计算机之间建立对应关系,就是将字符转换为计算机可以接受的数字代码。就是以二进制的数字来对应字符集的字符。

ASCII

  • ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统。
  • ASCII字符集:主要包括控制字符(回车键,退格键,换行键等);可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
  • ASCII编码:将ASCII字符集转换为计算机可以接受的数字系统的数的规则。使用7位(bits)表示一个字符,共128字符。

ISO-8859-1

  • ISO-8859-1编码是单字节编码,乡下兼容ASCII,是ASCII的扩展的一种,其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。
  • 此字符集支持部分于欧洲使用的语言,包括阿尔巴尼亚语、巴斯克语、布列塔尼语、加泰罗尼亚语、丹麦语、荷兰语、法罗语、弗里西语、加利西亚语、德语、格陵兰语、冰岛语、爱尔兰盖尔语、意大利语、拉丁语、卢森堡语、挪威语、葡萄牙语、里托罗曼斯语、苏格兰盖尔语、西班牙语及瑞典语。
  • Latin1是ISO-8859-1的别名,有些环境下写作Latin-1.

GB2312

  • 《信息交换用汉子编码字符》是由中国国家标准总局1980年发布,1981年5月1日开始实施的一套国家标准,标准号是GB2312-1980.
  • GB2312编码适用于汉字处理、汉字通讯等系统之间的信息交换,通行于中国大陆;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB2312.
  • 基本集共收入汉字6763个和非汉字图形字符682个。
  • GB2312的出现,基本满足了汉字的计算机处理需求,它所收录的汉字已经覆盖了中国大陆99.75%的使用频率。对于人名、古汉语等方面出现的罕用字,GB2312不能处理,这导致了后来GBK及GB18030汉字字符集的出现。

BIG5

  • Big5是在1984年由台湾13家厂商与台湾地区财团法人信息工业策进会为五大中文套装软件(宏碁、神通、佳佳、零壹、大众)所设计的中文内码,所以就称为Big5中文内码。
  • 大五码是使用繁体中文社群中最常用的电脑汉字字符集标准,共收录13060个中文文字,其中有二字为重复编码。

GB2312编码

  • GB2312一个汉字用两个字节表示。
  • 原则上,两个字节可以表示256X256=65536种不同的符号。
  • 考虑到汉字编码与其他国际通用编码,如ASCII西文字符编码的关系,我国国家标准局采用了加以修正的两字节汉字编码方案,只用了两个字节的低7位。这个方案可以容纳128X128=16384种不同的汉字;
  • 为了与标准ASCII码兼容,每个字节中都不能再用32个控制功能码和码值为32的空格以及127的操作码。
  • 所以每个字节只能有94个编码。这样,双七位实际能够表示的字数是:94X94=8836个。

区位码

  • 这个方阵实际上组成一个有94个区(编号由01到94),每个区有94个位(编号由01到94)的汉字字符集。
  • 一个汉字所在的区号和位号的组合就构成了该汉字的“区位码”。其中,高两位为区号,低两位为位号。这样区位码就可以唯一地确定某一汉字或字符;
  • 反之,任何一个汉字或符号都对应一个唯一的区位码,没有重码。如“保”字在二维代码表中处于17区第3位,区位码即“1703”。

国标码

  • 国标码是由区位码稍作转换得到的,是一个汉字国家标准的二进制编码。
  • 其转换方法为:先将十进制区码和位码转换为十六进制的区码和位码;
  • 这样就得了一个与国标码有一个相对位置差的编码,再将这个代码的第一个字节和第二个字节分别加上20H,就得到国际码。
  • 如:“保”字的国际码为3123H,它是经过下面的转换得到的:1703D→1103H→+20H→3123H。(20H就是十进制的32,上文提到了“但为了与标准ASCII码兼容,每个字节中都不能再用32个控制功能码和码值为32的空格以及127的操作码”)

机内码

  • 国标码是汉字信息交换的标注编码,但因其前后字节的最高位为0,与ASCII码发生冲突,如“保”字,国标码为31H和23H,而细纹字符“1”和“#”的ASCII为31H和23H,现加入内存中有两个字节为31H和23H;
  • 这到底是一个汉字,还是两个西文字符“1”和“#”?于是就出现了二义性,显然,国标码是不可能在计算机内部直接采用的。
  • 于是汉字的机内码采用变形国标码,其变形方法为:将国际码的每个字节都加上128,即将两个字节的最高位由0改1,其余7位不变,如:由上面我们知道“保”字的国标码为3123H,前字节为00110001B,后字节为00100011B,高位改1为10110001B和10100011B即为B1A3H,因此,保字的机内码就是B1A3H;机内码是一个汉字在计算机内部保存的编码。

区位码、国标码、机内码

  • 区位码前两个位换成16进制,然后后两个位换成16进制。
  • 国标码=区位码(十六进制)+2020H
  • 机内码=国标码+8080H
  • 10-09区为特殊符号
  • 16-55区为一级汉字,按拼音排序。
  • 56-87区为二级汉字,按部首/笔画排序。
  • 10-15区及88-94区则未有编码。

Unicode与UCS

  • 国际标准化组织(ISO)、多语言软件制造商组成的统一码联盟。
  • Unicode,为了世界上大多数文字系统进行整理和编码。
  • ISO组织也在做同样的事情,ISO开赞了ISO/IEC 10646项目,名字叫“Universal Multiple-Octet Coded Character Set”,简称UCS。
  • 后来,双方意识到石姐不需要2套通用的字符集,所以双方开始进行整合,到Unicode2.0时,Unicode的编码和UCS的编码都基本一致。

Unicode编码与UTF

  • 一个字符的Unicode编码是确定的。
  • 但是在实际传输过程中,由于不同系统平台的设计不一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式成为Unicode转换格式(Unicode translation format,简称UTF)。
  • 简单说,UCS或Unicode只是定义了从0-1114112这些数字各自对应的是什么字符,而计算机上是如何实现的就是UTF决定的。
  • UTF-8、UTF-16、UTF-32等都是Unicode的编码实现方式。

手工把Unicode转为UTF-8的编码

  1. “你好”的Unicode码“20320”“22909”
  2. 二进制:100 111101 100000,101 100101 111101
  3. 每个字从右往左填充1110xxxx 10xxxxxx 10xxxxxx(11100100 10111101 10100000,11100101 10100101 10111101)
  4. E4 BD A0,E5 A5 BD

UTF-8汉字到底占几个字节?

UTF-8的汉字在Unicode 800-FFFF之间,所以占三个字节。

UTF-16

  • UTF-16和上面提到的Unicode本身的编码规范是一致的。
  • 所有字符的编码长度为16位,也即2个字节。
  • 对ASCII字符来说,存储空间浪费严重。
  • 不兼容ASCII。

UTF的字节序和BOM

  • UTF-16编码每个字符占用了两个字节,在Macintosh(Mac)机和PC机上,对字节顺序的理解是不一致的。避免同一个文件造成错乱,如何解决呢?
  • Unicode规范中推荐的标记字节顺序的方法就是BOM(Byte Order Mark)。
    • 在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59.如果我们收到UTF-16字节流“594E”,那么这是“594E”,那么这是“奎”还是“乙”?
    • Unicode规范中推荐的标记字节顺序的方法就是BOM。
  • UTF-8不需要BOM来表名字节顺序,但可以用BOM来表名编码方式。

BOM含义

开头字节 Charset/encoding
EF BB BF UTF-8
FE FF UTF-16/UCS-2,little endian(UTF-16LE)
FF FE UTF-16/UCS-2,big endian(UTF-16BE)
FF FE 00 00 UTF-32/UCS-4,little endian
00 00 FE FF UTF-32/UCS-4,big-endia

如何判断一个汉字是否是UTF-8

方法1
  1. if (preg_match("/^([".chr(228)."-".chr(233)."]{1}[".chr(128)."-".chr(191)."]{1}[".chr(128)."-".chr(191)."]{1})+$/",$str)){
  2. echo '为UTF-8编码';
  3. }

方法2
  1. $re = strlen($str) > 6 ? '/([\xe0-\xef][\x80-\xbf]{2}){2}/' : '/[\xe0-\xef][\x80-\xbf]{2}/';
  2. $isutf8 = preg_match($re, $str);

如何判断一个汉字是否是GBK

  1. if (preg_match("/^([".chr(176)."-".chr(247)."]{1}[".chr(161)."-".chr(254)."]{1}){1}+$/", $str)){
  2. echo 'GB2312汉字';
  3. }

一定可以判断出汉字的编码么?

遇到下面的汉字就出问题了。
  1. <?php
  2. //echo(chr(230).chr(189));
  3. //echo(chr(189).chr(230));
  4. $str = '娼芥';
  5. $re = strlen($str) > 6 ? '/([\xe0-\xef][\x80-\xbf]{2}){2}/' : '/[\xe0-\xef][\x80-\xbf]{2}/';
  6. $isutf8 = preg_match($re, $str);
  7. var_dump($isutf8);

GBK和UTF8该如何选择?

  • 有限选择UTF-8,对程序员有利
  • 为了节省存储空间,选择GBK
  • 考虑其他系统兼容,适当选择
  • 要给外国人看(韩国人),支持多语言,就选UTF-8

发表评论

电子邮件地址不会被公开。 必填项已用*标注

Go