2015/09/26

常见编码方式


无论是在编辑文本文件的时候,还是在制作网页的时候,总会遇到文本编码方式的问题。如果处理不当,就会出现乱码。因此,有必要对文本的编码方式做一个详尽的了解。

常见的一些字符编码方式无非有:Unicode,ASCII,GBK,GB2312,UTF-8,BASE64。下面先对常见的这一些字符编码方式作下说明:


ASCII

这是美国在19世纪60年代的时候为了建立英文字符和二进制的关系时制定的编码规范,它能表示128个字符,其中包括英文字符、阿拉伯数字、西文字符以 及32个控制字符。它用一个字节来表示具体的字符,但它只用后7位来表示字符(2^7=128),最前面的一位统一规定为0

ASCII中的0~31为控制字符;32~126为打印字符;127为Delete(删除)命令。下表为控制字符释义

Bin(二进制) Dec(十进制) Hex(十六进制) 缩写/字符 解释
00000000 0 00 NUL(null) 空字符
00000001 1 01 SOH(start of headling) 标题开始
00000010 2 02 STX (start of text) 正文开始
00000011 3 03 ETX (end of text) 正文结束
00000100 4 04 EOT (end of transmission) 传输结束
00000101 5 05 ENQ (enquiry) 请求
00000110 6 06 ACK (acknowledge) 收到通知
00000111 7 07 BEL (bell) 响铃
00001000 8 08 BS (backspace) 退格
00001001 9 09 HT (horizontal tab) 水平制表符
00001010 10 0A LF (NL line feed, new line) 换行键
00001011 11 0B VT (vertical tab) 垂直制表符
00001100 12 0C FF (NP form feed, new page) 换页键
00001101 13 0D CR (carriage return) 回车键
00001110 14 0E SO (shift out) 不用切换
00001111 15 0F SI (shift in) 启用切换
00010000 16 10 DLE (data link escape) 数据链路转义
00010001 17 11 DC1 (device control 1) 设备控制1
00010010 18 12 DC2 (device control 2) 设备控制2
00010011 19 13 DC3 (device control 3) 设备控制3
00010100 20 14 DC4 (device control 4) 设备控制4
00010101 21 15 NAK (negative acknowledge) 拒绝接收
00010110 22 16 SYN (synchronous idle) 同步空闲
00010111 23 17 ETB (end of trans. block) 传输块结束
00011000 24 18 CAN (cancel) 取消
00011001 25 19 EM (end of medium) 介质中断
00011010 26 1A SUB (substitute) 替补
00011011 27 1B ESC (escape) 溢出
00011100 28 1C FS (file separator) 文件分割符
00011101 29 1D GS (group separator) 分组符
00011110 30 1E RS (record separator) 记录分离符
00011111 31 1F US (unit separator) 单元分隔符
00100000 32 20 (space) 空格
00100001 33 21 !
00100010 34 22 "
00100011 35 23 #
00100100 36 24 $
00100101 37 25 %
00100110 38 26 &
00100111 39 27 '
00101000 40 28 (
00101001 41 29 )
00101010 42 2A *
00101011 43 2B +
00101100 44 2C ,
00101101 45 2D -
00101110 46 2E .
00101111 47 2F /
00110000 48 30 0
00110001 49 31 1
00110010 50 32 2
00110011 51 33 3
00110100 52 34 4
00110101 53 35 5
00110110 54 36 6
00110111 55 37 7
00111000 56 38 8
00111001 57 39 9
00111010 58 3A :
00111011 59 3B ;
00111100 60 3C <
00111101 61 3D =
00111110 62 3E >
00111111 63 3F ?
01000000 64 40 @
01000001 65 41 A
01000010 66 42 B
01000011 67 43 C
01000100 68 44 D
01000101 69 45 E
01000110 70 46 F
01000111 71 47 G
01001000 72 48 H
01001001 73 49 I
01001010 74 4A J
01001011 75 4B K
01001100 76 4C L
01001101 77 4D M
01001110 78 4E N
01001111 79 4F O
01010000 80 50 P
01010001 81 51 Q
01010010 82 52 R
01010011 83 53 S
01010100 84 54 T
01010101 85 55 U
01010110 86 56 V
01010111 87 57 W
01011000 88 58 X
01011001 89 59 Y
01011010 90 5A Z
01011011 91 5B [
01011100 92 5C \
01011101 93 5D ]
01011110 94 5E ^
01011111 95 5F _
01100000 96 60 `
01100001 97 61 a
01100010 98 62 b
01100011 99 63 c
01100100 100 64 d
01100101 101 65 e
01100110 102 66 f
01100111 103 67 g
01101000 104 68 h
01101001 105 69 i
01101010 106 6A j
01101011 107 6B k
01101100 108 6C l
01101101 109 6D m
01101110 110 6E n
01101111 111 6F o
01110000 112 70 p
01110001 113 71 q
01110010 114 72 r
01110011 115 73 s
01110100 116 74 t
01110101 117 75 u
01110110 118 76 v
01110111 119 77 w
01111000 120 78 x
01111001 121 79 y
01111010 122 7A z
01111011 123 7B {
01111100 124 7C |
01111101 125 7D }
01111110 126 7E ~
01111111 127 7F DEL (delete) 删除

ASCII扩展

原本的ASCII码对于英文语言的国家是够用了,但是欧洲国家的一些语言会有拼音,这时7个字节就不够用了。因此一些欧洲国家就决定,利用字节中闲置的 最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。但这时 问题也出现了:不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码 中代表了é,在希伯 来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0—127表示的符号是一样的,不一样的只 是128—255的这一段。这个问题就直接促使了Unicode编码的产生。


Unicode符号集

正如上一节所说,世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则 用错误的编码方式解读,就会出现乱码。为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。而Unicode就是这样一种编码:它 包含了世界上所有的符号,并且每一个符号都是独一无二的。比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字“严”。具体的符号对应表,可以查询 unicode.org,或者专门的汉字对应表 。 很多人都说Unicode编码,但其实Unicode是一个符号集(世界上所有符号的符号集),而不是一种新的编码方式。

但是正因为Unicode包含了所有的字符,而有些国家的字符用一个字节便可以表示,而有些国家的字符要用多个字节才能表示出来。即产生了两个问题: 第一,如果有两个字节的数据,那计算机怎么知道这两个字节是表示一个汉字呢?还是表示两个英文字母呢?第二,因为不同字符需要的存储长度不一样, 那么如果Unicode规定用2个字节存储字符,那么英文字符存储时前面1个字节都是0,这就大大浪费了存储空间。

上面两个问题造成的结果是:

1)出现了unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示unicode。

2)unicode在很长一段时间内无法推广,直到互联网的出现。


UTF-8

互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种unicode的实现方式。其他实现方式还包括UTF-16和UTF-32, 不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一

UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

UTF-8的编码规则很简单,只有两条:

1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于0~127,完全跟 ASCII 相同。使用一个字节。

2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

Unicode编码(16进制) Dec(十进制) 幂表示 UTF-8(二进制) 所占字节数 描述
000000 - 00007F 0-127 0 - (2^7-1) 0xxxxxxx 1 ASCII码
000080 - 0007FF 128-2047 2^7 - (2^11-1) 110xxxxx, 10xxxxxx 2
000800 - 00FFFF 2048-65535 2^11 - (2^16-1) 1110xxxx, 10xxxxxx, 10xxxxxx 3 基本上所有中文都分布在这里
010000 - 10FFFF 65536-1114111 2^16 - (2^16 + 2^20-1) 11110xxx, 10xxxxxx, 10xxxxxx, 10xxxxxx 4


所以我们知道,为什么中文在UTF-8上占3个字节


GB2312

GB2312是适合中国人使用的编码,由中国国家标准总局发布。它主要分为两个部分:

127之前的符号;

127之后的符号

127之前的符号跟 ASCII 码所表示的意义相同。都是一个字节表示。127之后的符号统一用两个字节表示,包含了几乎所有的简体中文字。

台湾和香港那边喜欢用繁体字,GB2312 编码只包含简体中文,显然不够他们用,于是台湾和香港用包含了繁体字的 BIG5 编码,叫大五码。


GBK

GBK 编码的前半部分跟GB2312完全相同,还往后扩展了更多的汉字,127之后的符号统一用两个字节表示,包括几乎所有常见的不常见的汉字、繁体字、日语的平假名和片假名、俄文字母。 可以说GBK适用于中国大陆、台湾、香港、日本和俄国。虽然适合日本用,估计日本也不会用GBK 编码。确实,日本用的是JIS编码。 虽然增加了繁体字,但是GBK跟BIG5还是不兼容。

GBK和GB2312都是针对简体字的编码,只是GB2312只支持六千多个汉字的编码,而GBK支持1万多个汉字编码。


ANSI

ANSI编码不是一种特定的编码。

在简体中文系统下,ANSI编码代表着GB2312 编码;

在日文操作系统下,ANSI编码代表着JIS编码


Base64

Base64编码不像以上提到的编码,主要用于在计算机内表示字符的编码。Base64编码是网络上最常见的用于传输8Bit字节代码的编码方式之一。

Base64编码的思想是是采用64个基本的ASCII码字符对数据进行重新编码。它将需要编码的数据拆分成字节数组。以 3个字节 为一组。一共是3x8=24bit,再把这24bit数据分成 4组 ,即每组6个bit。 再在每组的的 最高位前补两个0 凑足一个字节。这样就把一个3字节为一组的数据重新编码成了4个字节。当所 要编码的数据的字节数不是3的整倍数,也就是说在分组时最后一组不够3个字节。这时 在最后一组填充1到2个0字节(\0)。并在最后编码完成后在结尾添加1到2个 “=”。Base64编码后要比源数据多33%

BASE64字符表:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/


test 的 Base64 为:dGVzdA==

把字符串进行每3个字符分一个组,后面不满3个补'\0',最后结果为:

    test\0\0

取test\0\0对应的ASCII码值:

    t(116), e(101), s(115), t(116), \0(0), \0(0)

然后把上面字节二进制码接起来:

    t(01110100), e(01100101), s(01110011), t(01110100), \0(00000000), \0(00000000)

再以每6位分一组,并在每组最高位填充两个0后形成1个字节的编码:

    00011101, 00000110, 00010101, 00110011, 00011101, 00000000, 00000000, 00000000

再把这每个字节数据转化成10进制数,然后根据BASE64给出的64个基本字符表,查出对应的ASCII码字符。注意,上面进行了2次补\0,所以这边最后两位字节不进行运算

    00011101(29), 00000110(6), 00010101(21), 00110011(51), 00011101(29), 00000000(0)

    29(d), 6(G), 21(V), 51(z), 29(d), 0(A)

最后连接各个base64码,并且每补偿一个\0,则添加一个"=",所以最后计算的结果为:

    dGVzdA==

可见,若看到一段数据只包含大小写字母、数字、加号+和正斜杠 / 以及末尾有可能有=,那么该编码十有八九是Base64编码。

Base64可用于简单的网络加密,但是它所实现的只是不能让你一眼就能看出来明文是什么。称它为编码还更适合一点。


动手实现一个PHP base64

<?php

//编码
function base64encode($str) {
    //64位编码表
    $base64_config = array(
    'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
    'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
    0,1,2,3,4,5,6,7,8,9,
    '+','/'
    );

    //拆分左右字符
    $str_arr = str_split($str);

    //不足3个字节,补'\0'
    $mod = count($str_arr)%3;
    $bmod = 0;
    if ($mod > 0) {
        $bmod = 3 - $mod;
        for ($i = 0;$i<$bmod;$i++) {
            $str_arr[] = "\0";
        }
    }

    //计算所有字符的二进制
    $bit = "";
    foreach ($str_arr as $value) {
        $ascii = ord($value); //计算ASCII码
        $bin = decbin($ascii); //转换成二进制
        $bin = str_pad($bin, 8, '0', STR_PAD_LEFT); //不足8位前面
        $bit .= $bin;
    }

    //每6位拆分一组
    $str_arr = str_split($bit, 6);

    $encode_str = "";
    for ($i=0; $i<(count($str_arr)-$bmod); $i++) { //注意这里,上面补'\0'的字节不计算base64
        $bin = "00".$str_arr[$i]; //每组前面补两个00形成一个字节
        $key = bindec($bin); //转换成10进制
        $base64 = $base64_config[$key]; //对照表格获取base64码
        $encode_str .= $base64;
    }
    $encode_str = str_pad($encode_str, strlen($encode_str)+$bmod, '=', STR_PAD_RIGHT);

    return $encode_str;
}

//解码
function base64decode($str) {
    //64位编码表
    $base64_config = array(
    'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
    'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
    0,1,2,3,4,5,6,7,8,9,
    '+','/'
    );

    $bmod = substr_count($str, "="); //计算补\0个数
    $str_arr = str_split($str);

    $bit = "";
    for ($i=0; $i<(count($str_arr)-$bmod); $i++) { //不计算\0
        $key = array_search($str_arr[$i], $base64_config); //获取base64对应的key
        $bin = decbin($key); //转换成二进制
        $bin = str_pad($bin, 8, '0', STR_PAD_LEFT); //不足8位前面
        $bin = substr($bin, 2); //去除前面两位0
        $bit .= $bin;
    }

    $str_arr = str_split($bit, 8);//每8位一组进行拆分
    $decode_str = "";
    foreach ($str_arr as $value) {
        $ascii = bindec($value);//转换成ASCII码
        $char = chr($ascii); //根据ascii获取对应字符
        $decode_str .= $char; //串联起来
    }
    return $decode_str;
}

//urlencode
function url_encode($str) {
    $str_arr = str_split($str);
    $encode_str = "";
    for ($i=0; $i<count($str_arr); $i++) {
        preg_match($str_arr[$i], "[^A-Za-z0-9_]");
    }
}

$en = base64encode("我阿斯蒂芬333说实在的sdfsfsdfsd");
$de = base64decode($en);

输出结果

5oiR6Zi/5pav6JKC6IqsMzMz6K+05a6e5Zyo55qEc2Rmc2ZzZGZzZA==
我阴斯蒂芬333譴实在的sdfsfsdfsd

urlencode & urldecode

URLEncode:是指针对网页url中的中文字符的一种编码转化方式,最常见的就是Baidu、Google等搜索引擎中输入中文查询时候,生成经过Encode过的网页URL。

URLEncode的方式一般有两种,一种是传统的基于GB2312的Encode(Baidu、Yisou等使用),另一种是基于UTF-8的Encode(Google、Yahoo等使用)。

URLEncode的原理是 除了 - _ . 之外的所有非字母数字字符都将被替换成百分号 % 后跟两位十六进制数

中文  ->  GB2312的Encode   ->  %D6%D0%CE%C4

中文  ->  UTF-8的Encode    ->  %E4%B8%AD%E6%96%87

PHP实现urlencode 和 urldecode


<?php

//demo.php

//urlencode
function url_encode($str) {
    $str_arr = str_split($str);
    $encode_str = "";
    for ($i=0; $i<count($str_arr); $i++) {
        if (preg_match("/[^A-Za-z0-9_\-\.]/", $str_arr[$i], $match)) {
            $char = "%".bin2hex($match[0]);
            $encode_str .= $char;
        }else {
            $encode_str .= $str_arr[$i];
        }
    }
    return $encode_str;
}

//urldecode
function url_decode($str) {
    preg_match_all("/%\w{2}/", $str, $match);
    foreach ($match[0] as $key => $value) {
        $char = substr($value, 1);
        $char = chr(hexdec($char));
        $str = str_replace($value, $char, $str);
    }
    return $str;
}

$en = url_encode("中文");
echo $en."\n";

我们可以修改demo.php编码方式来观察输出的结果

demo.php 编码方式为 GBK:%d6%d0%ce%c4

demo.php 编码方式为 UTF-8:%e4%b8%ad%e6%96%87

参考资料

http://blog.csdn.net/csywwx2008/article/details/17137097

http://www.cnblogs.com/infosec-hoary/archive/2012/02/28/2371385.html

http://www.cnblogs.com/reonlyrun/archive/2006/12/29/640991.html

http://blog.sina.com.cn/s/blog_4f9fc6e10100ui1p.html

http://www.jb51.net/article/27954.htm