字符编码
简介
- 起初再考虑写不写这篇文章,感觉这篇文章比较枯燥乏味,而且自己感觉也没理解的太透彻,就把理解的记录下来,所以这是纪念版的
- 前方高能,非战斗人员请迅速撤离,我要开始装逼了。
Go hard or go home 要么全力以赴,要么走人 No person has the right to rain on your dreams,you do it yourself. 没有人有权利给你的梦想泼冷水,只有你自己给自己的梦想泼冷水
看到这样的文字是不是很励志?那换一种方式你还会这样想吗? 16进制版:复制代码
476f2068617264206f7220676f20686f6d652089814e485168529b4ee58d742c89814e488d704eba20a4e6f20706572736f6e20206861732074686520726967687420746f207261696e206f6e20796f757220647265616d732c796f7520646f20697420796f757273656c662e206ca167094eba6709674352297ed94f60768468a660f36cfc51b76c342c53ea67094f6081ea5df17ed981ea5df1768468a660f36cfc51b76c34
然而他的字符编码是GB2312的,叫我转化成易懂的字符串,当时我就懵b了。因为当时我对字符编码一窍不通,然后就网上,查啊查,最后终于想到了解决方案复制代码
几个值的深思的问题
- 什么是字符?
字符是各种文字和符号的总称,包括各个国家文字、标点符号、图形符号、数字等。
- 什么是字符集?
字符集是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,常见字符集有:ASCII字符集、ISO 8859字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等
- 什么是字符编码?
1、 计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。 2、 字符编码(encoding)和字符集不同。字符集只是字符的集合,不一定适合作网络传送、处理,有时须经编码(encode)后才能应用。如Unicode可依不同需要以UTF-8、UTF-16、UTF-32等方式编码。 3、字符编码就是以二进制的数字来对应字符集的字符。 因此,对字符进行编码,是信息交流的技术基础。
- 概括
1、使用哪些字符。也就是说哪些汉字,字母和符号会被收入标准中。所包含“字符”的集合就叫做“字符集”。 2、规定每个“字符”分别用一个字节还是多个字节存储,用哪些字节来存储,这个规定就叫做“编码”。 3、各个国家和地区在制定编码标准的时候,“字符的集合”和“编码”一般都是同时制定的。因此,平常我们所说的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”这层含义外,同时也包含了“编码”的含义。 4、注意:Unicode字符集有多种编码方式,如UTF-8、UTF-16等;ASCII只有一种;大多数MBCS(包括GB2312,GBK)也只有一种。
- 有趣的例子
1、在显示器上看见的文字、图片等信息在电脑里面,其实并不是我们看见的样子,即使你知道所有信息都存储在硬盘里,把它拆开也看不见里面有任何东西,只有些盘片。假设,你用显微镜把盘片放大,会看见盘片表面凹凸不平,凸起的地方被磁化,凹的地方是没有被磁化;凸起的地方代表数字1,凹的地方代表数字0。硬盘只能用0和1来表示所有文字、图片等信息。 2、那么字母”A”在硬盘上是如何存储的呢?可能小张计算机存储字母”A”是1100001,而小王存储字母”A”是11000010,这样双方交换信息时就会误解。比如小张把1100001发送给小王,小王并不认为1100001是字母”A”,可能认为这是字母”X”,于是小王在用记事本访问存储在硬盘上的1100001时,在屏幕上显示的就是字母”X”。也就是说,小张和小王使用了不同的编码表。小张用的编码表是ASCII,ASCII编码表把26个字母都一一的对应到2进制1和0上;小王用的编码表可能是EBCDIC,只不过EBCDIC编码与ASCII编码中的字母和01的对应关系不同。一般地说,开放的操作系统(LINUX 、WINDOWS等)采用ASCII 编码,而大型主机系统(MVS 、OS/390等)采用EBCDIC 编码。在发送数据给对方前,需要事先告知对方自己所使用的编码,或者通过转码,使不同编码方案的两个系统可沟通自如。
- 这个例子说明了三点
1、不管是任何文字图片等,最后都会以二进制的形式储存到电脑的磁盘中(比如记事本A.txt,内容为"ABC"文件,在此磁盘中表现的就是01 01这种二进制形式) 盘片表面凹凸不平,凸起的地方被磁化,凹的地方是没有被磁化,凸起的地方代表数字1,凹的地方代表数字0。硬盘只能用0和1来表示所有文字、图片等信息。是的 很强势 2、 任何文件要储存到电脑中,都会事先进行编码,然后储存到电脑的磁盘中,比如A.txt文件,默认编码为ANSI编码,也可以编码为UTF-8,然而不同的编码方式 对应着计算机用一个字节还是多个字节存储,用哪些字节来存储。 3、在双方数据进行通讯时,要么就保证发送方和接受方的数据编码是相同,要么就是其中一方需要转码
- 什么是字节和位?
字节byte和位bit是电脑里的数据量单位。 1.按计算机中的规定,一个英文的字符占用一个字节,而一个汉字以及汉字的标点符号、字符都占用两个字节。 2.1个字节等于8位 1byte=8bit 3.1bit在磁盘中以二进制01的形式保存 凸起的地方代表数字1,凹的地方代表数字0
字符编码种类
ASCII
ASCII码是西欧编码的方式,采取7位编码,所以是2^7=128,共可以表示128个字符,包括34个字符,(如换行LF,回车CR等),其余94位为英文字母和标点符号及运算符号等。
重点:
字符集:从符号(NUL="/0"=“空操作字符”)到“Z”再到“DEL”符号 字符编码范围:二进制:00000000——01111111 十进制:0-127 占用字节:1字节 8bit 盘片储存方式:凹凹凹凹凹凹凹凹——凸凸凸凸凸凸凸凸
注:NUL:‘\0'是一个ASCII码为0的字符,从ASCII码表中可以看到ASCII码为0的字符是“空操作字符”,它不引起任何控制动作,也不是一个可显示的字符。
但我们发现ASCII码是没有中文编码的,显然在天朝是不够用的,于是GB2312诞生了。 ###GB2321 GB2312 是对 ASCII 的中文扩展。兼容ASCII。
编码规定: 编码小于127的字符与ASCII编码相同, 特性:两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。
字符集:从符号(NUL="/0"=“空操作字符”)到“Z”到“齄"(简体中文) 字符编码范围:16进制:0x0000-(中间有一部分是未使用的)-0xF7FE 占用字节:英文 1字节 8bit 盘片储存方式:凹凹凹凹凹凹凹凹——凸凸凸凸凸凸凸凸 中文 2字节 16bit 凹凹凹凹凹凹凹凹凹凹凹凹凹凹凹凹——...
GBK
GBK 兼容ASCLL 兼容 GB2312 是GB2312的扩展 但是中国的汉字太多了,我们很快就就发现有许多人的人名没有办法在这里打出来,不得不继续把 GB2312 没有用到的码位找出来用上。后来还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 “GBK” 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。 ###Unicode Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。 目前的Unicode字符分为17组编排,0x0000至0x10FFFF,每组称为平面(Plane),而每平面拥有65536个码位,共1114112个。然而目前只用了少数平面。UTF-8、UTF-16、UTF-32都是将数字转换到程序数据的编码方案。
UTF-8
UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式如下: UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是6个字节。从上表可以看出,6字节模板有31个x,即可以容纳31位二进制数字。Unicode的最大码位0x7FFFFFFF也只有31位。 例1:“汉”字的Unicode编码是0x6C49。0x6C49在0x0800-0xFFFF之间,使用用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是:0110 1100 0100 1001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。 举一个例子:It's 知乎日报
你看到的unicode字符集是这样的编码表:
I 0049 t 0074 ' 0027 s 0073 0020 知 77e5 乎 4e4e 日 65e5 报 62a5 每一个字符对应一个十六进制数字。
计算机只懂二进制,因此,严格按照unicode的方式(UCS-2),应该这样存储:
I 00000000 01001001 t 00000000 01110100 ' 00000000 00100111 s 00000000 01110011 00000000 00100000 知 01110111 11100101 乎 01001110 01001110 日 01100101 11100101 报 01100010 10100101 这个字符串总共占用了18个字节,但是对比中英文的二进制码,可以发现,英文前9位都是0!浪费啊,浪费硬盘,浪费流量。
怎么办?
UTF
UTF-8是这样做的:
- 单字节的字符,字节的第一位设为0,对于英语文本,UTF-8码只占用一个字节,和ASCII码完全相同;
- n个字节的字符(n>1),第一字节的前n位设为1,第n+1位设为0,后面字节的前两位都设为10,这n个字节的其余空位填充该字符unicode码,高位用0补足。
这样就形成了如下的UTF-8标记位:
高位字节 | 低位字节 | 低位字节 | 低位字节 | 低位字节 | 低位字节 |
---|---|---|---|---|---|
0xxxxxxx | |||||
110xxxxx | 10xxxxxx | ||||
1110xxxx | 10xxxxxx | 10xxxxxx | |||
11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx | ||
111110xx | 10xxxxxx | 10xxxxxx | 10xxxxxx | 10xxxxxx | |
1111110x | 10xxxxxx | 10xxxxxx | 10xxxxxx | 10xxxxxx | 10xxxxxx |
... .... |
一图解忧愁
1.从这个可以看出,同样的字符集,但unicode编码和gbk编码是不同的。,所以unicode字符集不兼容gbk字符集 2.只要知道unicode字符集的编码表,就可以用UTF8编码规则找到UTF-8对应的汉字编码
解决问题
从上面的内容了解了字符编码以后,以后遇到相关的字符编码问题的时候至少有解决的思路,而不是一头雾水
分析
NodeJS服务端环境下 476f2068617264206f7220676f20686f6d652089814e485168529b4ee58d742c89814e488d704eba20a4e6f20706572736f6e20206861732074686520726967687420746f207261696e206f6e20796f757220647265616d732c796f7520646f20697420796f757273656c662e206ca167094eba6709674352297ed94f60768468a660f36cfc51b76c342c53ea67094f6081ea5df17ed981ea5df1768468a660f36cfc51b76c34 容易产生误区: 这个问题的情况并不是字符乱码问题,而只是怎样解析16进制gb2312字符,只是利用了字符编码的原理。 1.我接受的是gb2312格式的数据,但是这里并没有乱码,因为服务器发过来的是数字和英文,gb2312是兼容ASCII的。 2.我设置了(接受响应数据编码格式)response.setEncoding('gb2312');即使我不设置响应格式,nodejs默认识utf-8的,utf-8和gbk都是兼容ASCII,也是就是支持英文和数字
var http=require('http');var Iconv = require('iconv-lite');//转码数据var GetHttp=function(options,callback){var AllData="";try{var GetReq = http.request(options, function (res) { console.log('STATUS: ' + res.statusCode); res.setEncoding('gb2312'); if(res.statusCode==200){ res.on('data', function (chunk) { AllData+=chunk;}) .on('end',function(){callback(200,AllData);}) }else{callback(500,'error');}console.log(AllData);}); GetReq.on('error',function(err){callback(500,err)}); GetReq.end();}catch(error){callback(500,error);}}exports.GetHttp=GetHttp;复制代码
开始问题分析: 1.字符集分析:gb2312支持数字和英文和6000+汉字 2.编码分析:英文占一个字节,中文占两个字节(这就是问题)
//1.fromCharCode() 可接受一个指定的 Unicode 值,然后返回一个字符串。但我们的数据是gb2312的编码数据,然而gbk和unicode的编码方式又不一样,所以解析出来的数据会乱码//2.利用下面的代码,中文也会乱码,因为英文占1个字节,中文占2个字节,1个字节是8个二进制流的bit=2个16进制流的bit,而中文=4个16进制流的bit,下面的代码相当于把1个16进制的数转为字符function HexTostring(s) { var r = ""; for (var i = 0; i < s.length; i += 2) { var sxx = parseInt(s.substring(i, i + 2), 16); r += String.fromCharCode(sxx); } return r;}复制代码
这时就要想到,中文汉子对照表:
解决方案
-
首先把汉子编码对照表存入以存入数据库(mongodb)
-
获取,并以key=gbk16进制编码 value=汉子的形式存下来
var dicUniCodeCN=new Array(); DBTool.FindData('mongodb://数据库地址/数据库名','unicodeCN',{},function(Docs){ if (Docs.length>0) { for (var i = 0; i < Docs.length; i++) { dicUniCodeCN[Docs[i].gbk16.toString().toUpperCase()]=Docs[i].CN; }; }});复制代码
3.特性:gb2312的高位字节如果大于127(ASCII),就为中文,只有gb2312具有这个特性
var simpleCNStr=""; for (var j = 0; j < hexData.length; j += 2){//高位字节>127为中文var strHex=hexData.substring(j,j+2);console.log(parseInt("0x"+strHex,16));if (parseInt("0x"+strHex,16)>127) { strHex=hexData.substring(j,j+4); j+=2; simpleCNStr+=dicUniCodeCN[strHex];}else{ simpleCNStr+=String.fromCharCode(parseInt(strHex,16));}}复制代码
4.如果想兼容utf-8和unicode和gbk,那么可以4位16进制的字符截取,如果大于127,那么默认为中文,否则就是英文或字符或数字
var simpleCNStr=""; for (var j = 0; j < hexData.length; j += 4){//4位截取,大于127的为中文var strHex=hexData.substring(j,j+4);console.log(parseInt("0x"+strHex,16));if (parseInt("0x"+strHex,16)>127) {//不想写了 }else{//待续 你们写吧...}}复制代码
题外话-关于parseInt(string, radix)
parseInt("10"); //返回 10parseInt("19",10); //返回 19 (10+9)parseInt("11",2); //返回 3 (2+1)parseInt("17",8); //返回 15 (8+7)parseInt("1f",16); //返回 31 (16+15)parseInt("010"); //未定:返回 10 或 8复制代码
这个函数是把数字或进制字符都转为10进制的数字,第二个参数radix表示的是第一个参数string的类型(10进制,2进制,8进制,16进制),我之前很白菜的理解为我想把第一个参数string转化成16进制。哎,我还是太年轻啊