深刻理解-字符编码ASCII,GB2312,GBK,Unicode,UTF-8

字符编码

简介

  1. 起初再考虑写不写这篇文章,感受这篇文章比较枯燥乏味,并且本身感受也没理解的太透彻,就把理解的记录下来,因此这是记念版的
  2. 前方高能,非战斗人员请迅速撤离,我要开始装逼了。

Go hard or go home 要么尽心尽力,要么走人 No person has the right to rain on your dreams,you do it yourself. 没有人有权利给你的梦想泼冷水,只有你本身给本身的梦想泼冷水node

看到这样的文字是否是很励志?那换一种方式你还会这样想吗? 16进制版:
复制代码

476f2068617264206f7220676f20686f6d652089814e485168529b4ee58d742c89814e488d704eba20a4e6f20706572736f6e20206861732074686520726967687420746f207261696e206f6e20796f757220647265616d732c796f7520646f20697420796f757273656c662e206ca167094eba6709674352297ed94f60768468a660f36cfc51b76c342c53ea67094f6081ea5df17ed981ea5df1768468a660f36cfc51b76c34mongodb

然而他的字符编码是GB2312的,叫我转化成易懂的字符串,当时我就懵b了。由于当时我对字符编码一窍不通,而后就网上,查啊查,最后终于想到了解决方案
复制代码

几个值的深思的问题

  1. 什么是字符?

字符是各类文字和符号的总称,包括各个国家文字、标点符号、图形符号、数字等。数据库

  1. 什么是字符集?

字符集是多个字符的集合,字符集种类较多,每一个字符集包含的字符个数不一样,常见字符集有:ASCII字符集、ISO 8859字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等bash

  1. 什么是字符编码?

一、 计算机要准确的处理各类字符集文字,须要进行字符编码,以便计算机可以识别和存储各类文字。 二、 字符编码(encoding)和字符集不一样。字符集只是字符的集合,不必定适合做网络传送、处理,有时须经编码(encode)后才能应用。如Unicode可依不一样须要以UTF-八、UTF-1六、UTF-32等方式编码。 三、字符编码就是以二进制的数字来对应字符集的字符。 所以,对字符进行编码,是信息交流的技术基础。服务器

  1. 归纳

一、使用哪些字符。也就是说哪些汉字,字母和符号会被收入标准中。所包含“字符”的集合就叫作“字符集”。 二、规定每一个“字符”分别用一个字节仍是多个字节存储,用哪些字节来存储,这个规定就叫作“编码”。 三、各个国家和地区在制定编码标准的时候,“字符的集合”和“编码”通常都是同时制定的。所以,日常咱们所说的“字符集”,好比:GB2312, GBK, JIS 等,除了有“字符的集合”这层含义外,同时也包含了“编码”的含义。 四、注意:Unicode字符集有多种编码方式,如UTF-八、UTF-16等;ASCII只有一种;大多数MBCS(包括GB2312,GBK)也只有一种。网络

  1. 有趣的例子

一、在显示器上看见的文字、图片等信息在电脑里面,其实并非咱们看见的样子,即便你知道全部信息都存储在硬盘里,把它拆开也看不见里面有任何东西,只有些盘片。假设,你用显微镜把盘片放大,会看见盘片表面凹凸不平,凸起的地方被磁化,凹的地方是没有被磁化;凸起的地方表明数字1,凹的地方表明数字0。硬盘只能用0和1来表示全部文字、图片等信息。 二、那么字母”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来表示全部文字、图片等信息。是的 很强势 二、 任何文件要储存到电脑中,都会事先进行编码,而后储存到电脑的磁盘中,好比A.txt文件,默认编码为ANSI编码,也能够编码为UTF-8,然而不一样的编码方式 对应着计算机用一个字节仍是多个字节存储,用哪些字节来存储。 三、在双方数据进行通信时,要么就保证发送方和接受方的数据编码是相同,要么就是其中一方须要转码ui

  1. 什么是字节和位?

字节byte和位bit是电脑里的数据量单位。 1.按计算机中的规定,一个英文的字符占用一个字节,而一个汉字以及汉字的标点符号、字符都占用两个字节。 2.1个字节等于8位 1byte=8bit 3.1bit在磁盘中以二进制01的形式保存 凸起的地方表明数字1,凹的地方表明数字0编码

字符编码种类

ASCII

ASCII码是西欧编码的方式,采起7位编码,因此是2^7=128,共能够表示128个字符,包括34个字符,(如换行LF,回车CR等),其他94位为英文字母和标点符号及运算符号等。spa

ASCII表

重点:

字符集:从符号(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-八、UTF-1六、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是这样作的:

  1. 单字节的字符,字节的第一位设为0,对于英语文本,UTF-8码只占用一个字节,和ASCII码彻底相同;
  1. 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
... ....

>好比"知"字 在Unicode中占用两个字节,那么第一字节(我叫它高位字节)的前两位设位1,第三位设为10,后面低位字节设为前两位设为10, "知"→ 11100111 10011111 10100101 怎么知道“知”字占用两个字节的?首先要知道Unicode字符集中,“知”字的编码为77e5,而后转化为二进制流01110111 11100101的bit,每8bit等于1byte 因此就占两个字节 >因而,”It's 知乎日报“就变成了: I 01001001 t 01110100 ' 00100111 s 01110011 00100000 知 11100111 10011111 10100101 乎 11100100 10111001 10001110 日 11100110 10010111 10100101 报 11100110 10001010 10100101 和上边的方案对比一下,英文短了,每一个中文字符却多用了一个字节。可是整个字符串只用了17个字节,比上边的18个短了一点点。 剧透:一切都是为了节省你的硬盘和流量。

一图解忧愁

汉子对照表

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;
}
复制代码

这时就要想到,中文汉子对照表:

汉子对照表

解决方案

  1. 首先把汉子编码对照表存入以存入数据库(mongodb)

  2. 获取,并以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");			//返回 10
parseInt("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进制。哎,我仍是太年轻啊

相关文章
相关标签/搜索