深刻理解-字符編碼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進制。哎,我仍是太年輕啊

相關文章
相關標籤/搜索