字符編碼是計(jì)算機(jī)編程中不可回避的問(wèn)題,不管你用 Python2 還是 Python3,亦或是 C++, Java 等,我都覺(jué)得非常有必要厘清計(jì)算機(jī)中的字符編碼概念。本文主要分以下幾個(gè)部分介紹:
基本概念
常見(jiàn)字符編碼簡(jiǎn)介
Python 的默認(rèn)編碼
Python2 中的字符類型
UnicodeEncodeError & UnicodeDecodeError 根源
基本概念
在電腦和電信領(lǐng)域中,字符是一個(gè)信息單位,它是各種文字和符號(hào)的總稱,包括各國(guó)家文字、標(biāo)點(diǎn)符號(hào)、圖形符號(hào)、數(shù)字等。比如,一個(gè)漢字,一個(gè)英文字母,一個(gè)標(biāo)點(diǎn)符號(hào)等都是一個(gè)字符。
字符集(Character set)
字符集是字符的集合。字符集的種類較多,每個(gè)字符集包含的字符個(gè)數(shù)也不同。比如,常見(jiàn)的字符集有 ASCII 字符集、GB2312 字符集、Unicode 字符集等,其中,ASCII 字符集共有 128 個(gè)字符,包含可顯示字符(比如英文大小寫(xiě)字符、阿拉伯?dāng)?shù)字)和控制字符(比如空格鍵、回車(chē)鍵);GB2312 字符集是中國(guó)國(guó)家標(biāo)準(zhǔn)的簡(jiǎn)體中文字符集,包含簡(jiǎn)化漢字、一般符號(hào)、數(shù)字等;Unicode 字符集則包含了世界各國(guó)語(yǔ)言中使用到的所有字符,
字符編碼(Character encoding)
字符編碼,是指對(duì)于字符集中的字符,將其編碼為特定的二進(jìn)制數(shù),以便計(jì)算機(jī)處理。常見(jiàn)的字符編碼有 ASCII 編碼,UTF-8 編碼,GBK 編碼等。一般而言,字符集和字符編碼往往被認(rèn)為是同義的概念,比如,對(duì)于字符集 ASCII,它除了有「字符的集合」這層含義外,同時(shí)也包含了「編碼」的含義,也就是說(shuō),ASCII 既表示了字符集也表示了對(duì)應(yīng)的字符編碼。
下面我們用一個(gè)表格做下總結(jié):
常見(jiàn)字符編碼簡(jiǎn)介
常見(jiàn)的字符編碼有 ASCII 編碼,GBK 編碼,Unicode 編碼和 UTF-8 編碼等等。這里,我們主要介紹 ASCII、Unicode 和 UTF-8。
ASCII
計(jì)算機(jī)是在美國(guó)誕生的,人家用的是英語(yǔ),而在英語(yǔ)的世界里,不過(guò)就是英文字母,數(shù)字和一些普通符號(hào)的組合而已。
在 20 世紀(jì) 60 年代,美國(guó)制定了一套字符編碼方案,規(guī)定了英文字母,數(shù)字和一些普通符號(hào)跟二進(jìn)制的轉(zhuǎn)換關(guān)系,被稱為 ASCII (American Standard Code for Information Interchange,美國(guó)信息互換標(biāo)準(zhǔn)編碼) 碼。
比如,大寫(xiě)英文字母 A 的二進(jìn)制表示是 01000001(十進(jìn)制 65),小寫(xiě)英文字母 a 的二進(jìn)制表示是 01100001 (十進(jìn)制 97),空格 SPACE 的二進(jìn)制表示是 00100000(十進(jìn)制 32)。
Unicode
ASCII 碼只規(guī)定了 128 個(gè)字符的編碼,這在美國(guó)是夠用的。可是,計(jì)算機(jī)后來(lái)傳到了歐洲,亞洲,乃至世界各地,而世界各國(guó)的語(yǔ)言幾乎是完全不一樣的,用 ASCII 碼來(lái)表示其他語(yǔ)言是遠(yuǎn)遠(yuǎn)不夠的,所以,不同的國(guó)家和地區(qū)又制定了自己的編碼方案,比如中國(guó)大陸的 GB2312 編碼 和 GBK 編碼等,日本的 Shift_JIS 編碼等等。
雖然各個(gè)國(guó)家和地區(qū)可以制定自己的編碼方案,但不同國(guó)家和地區(qū)的計(jì)算機(jī)在數(shù)據(jù)傳輸?shù)倪^(guò)程中就會(huì)出現(xiàn)各種各樣的亂碼(mojibake),這無(wú)疑是個(gè)災(zāi)難。
怎么辦?想法也很簡(jiǎn)單,就是將全世界所有的語(yǔ)言統(tǒng)一成一套編碼方案,這套編碼方案就叫 Unicode,它為每種語(yǔ)言的每個(gè)字符設(shè)定了獨(dú)一無(wú)二的二進(jìn)制編碼,這樣就可以跨語(yǔ)言,跨平臺(tái)進(jìn)行文本處理了,是不是很棒!
Unicode 1.0 版誕生于 1991 年 10 月,至今它仍在不斷增修,每個(gè)新版本都會(huì)加入更多新的字符,目前最新的版本為 2016 年 6 月 21 日公布的 9.0.0。
Unicode 標(biāo)準(zhǔn)使用十六進(jìn)制數(shù)字,而且在數(shù)字前面加上前綴 U+,比如,大寫(xiě)字母「A」的 unicode 編碼為 U+0041,漢字「嚴(yán)」的 unicode 編碼為 U+4E25。更多的符號(hào)對(duì)應(yīng)表,可以查詢 unicode.org,或者專門(mén)的漢字對(duì)應(yīng)表。
UTF-8
Unicode 看起來(lái)已經(jīng)很完美了,實(shí)現(xiàn)了大一統(tǒng)。但是,Unicode 卻存在一個(gè)很大的問(wèn)題:資源浪費(fèi)。
為什么這么說(shuō)呢?原來(lái),Unicode 為了能表示世界各國(guó)所有文字,一開(kāi)始用兩個(gè)字節(jié),后來(lái)發(fā)現(xiàn)兩個(gè)字節(jié)不夠用,又用了四個(gè)字節(jié)。比如,漢字「嚴(yán)」的 unicode 編碼是十六進(jìn)制數(shù) 4E25,轉(zhuǎn)換成二進(jìn)制有十五位,即 100111000100101,因此至少需要兩個(gè)字節(jié)才能表示這個(gè)漢字,但是對(duì)于其他的字符,就可能需要三個(gè)或四個(gè)字節(jié),甚至更多。
這時(shí),問(wèn)題就來(lái)了,如果以前的 ASCII 字符集也用這種方式來(lái)表示,那豈不是很浪費(fèi)存儲(chǔ)空間。比如,大寫(xiě)字母「A」的二進(jìn)制編碼為 01000001,它只需要一個(gè)字節(jié)就夠了,如果 unicode 統(tǒng)一使用三個(gè)字節(jié)或四個(gè)字節(jié)來(lái)表示字符,那「A」的二進(jìn)制編碼的前面幾個(gè)字節(jié)就都是 0,這是很浪費(fèi)存儲(chǔ)空間的。
為了解決這個(gè)問(wèn)題,在 Unicode 的基礎(chǔ)上,人們實(shí)現(xiàn)了 UTF-16, UTF-32 和 UTF-8。下面只說(shuō)一下 UTF-8。
UTF-8 (8-bit Unicode Transformation Format) 是一種針對(duì) Unicode 的可變長(zhǎng)度字符編碼,它使用一到四個(gè)字節(jié)來(lái)表示字符,例如,ASCII 字符繼續(xù)使用一個(gè)字節(jié)編碼,阿拉伯文、希臘文等使用兩個(gè)字節(jié)編碼,常用漢字使用三個(gè)字節(jié)編碼,等等。
因此,我們說(shuō),UTF-8 是 Unicode 的實(shí)現(xiàn)方式之一,其他實(shí)現(xiàn)方式還包括 UTF-16(字符用兩個(gè)或四個(gè)字節(jié)表示)和 UTF-32(字符用四個(gè)字節(jié)表示)。
Python 的默認(rèn)編碼
Python2 的默認(rèn)編碼是 ascii,Python3 的默認(rèn)編碼是 utf-8,可以通過(guò)下面的方式獲取:
Python2
Python2.7.11(default,Feb242016,10:48:05)
[GCC4.2.1Compatible AppleLLVM7.0.2(clang-700.1.81)]on darwin
Type"help","copyright","credits"or"license"formoreinformation.
>>>importsys
>>>sys.getdefaultencoding()
'ascii'
Python3
Python3.5.2(default,Jun292016,13:43:58)
[GCC4.2.1Compatible AppleLLVM7.3.0(clang-703.0.31)]on darwin
Type"help","copyright","credits"or"license"formoreinformation.
>>>importsys
>>>sys.getdefaultencoding()
'utf-8'
Python2 中的字符類型
Python2 中有兩種和字符串相關(guān)的類型:str 和 unicode,它們的父類是 basestring。其中,str 類型的字符串有多種編碼方式,默認(rèn)是 ascii,還有 gbk,utf-8 等,unicode 類型的字符串使用 u'...' 的形式來(lái)表示,下面的圖展示了 str 和 unicode 之間的關(guān)系:
兩種字符串的相互轉(zhuǎn)換概括如下:
把 UTF-8 編碼表示的字符串 ‘xxx’ 轉(zhuǎn)換為 Unicode 字符串 u’xxx’ 用 decode('utf-8') 方法:
>>>'中文'.decode('utf-8')
u'中文'
把 u’xxx’ 轉(zhuǎn)換為 UTF-8 編碼的 ‘xxx’ 用 encode('utf-8') 方法:
>>>u'中文'.encode('utf-8')
'??-???'
UnicodeEncodeError & UnicodeDecodeError 根源
用 Python2 編寫(xiě)程序的時(shí)候經(jīng)常會(huì)遇到 UnicodeEncodeError 和 UnicodeDecodeError,它們出現(xiàn)的根源就是如果代碼里面混合使用了 str 類型和 unicode 類型的字符串,Python 會(huì)默認(rèn)使用 ascii 編碼嘗試對(duì) unicode 類型的字符串編碼 (encode),或?qū)?str 類型的字符串解碼 (decode),這時(shí)就很可能出現(xiàn)上述錯(cuò)誤。
下面有兩個(gè)常見(jiàn)的場(chǎng)景,我們最好牢牢記住:
在進(jìn)行同時(shí)包含 str 類型和 unicode 類型的字符串操作時(shí),Python2 一律都把 str 解碼(decode)成 unicode 再運(yùn)算,這時(shí)就很容易出現(xiàn) UnicodeDecodeError。
讓我們看看例子:
>>>s='你好'# str 類型, utf-8 編碼
>>>u=u'世界'# unicode 類型
>>>s+u# 會(huì)進(jìn)行隱式轉(zhuǎn)換,即 s.decode('ascii') + u
Traceback(most recent calllast):
File"
UnicodeDecodeError:'ascii'codeccan'tdecodebyte0xe4inposition0:ordinalnotinrange(128)
為了避免出錯(cuò),我們就需要顯示指定使用 ‘utf-8’ 進(jìn)行解碼,如下:
>>>s='你好'# str 類型,utf-8 編碼
>>>u=u'世界'
>>>
>>>s.decode('utf-8')+u# 顯示指定 'utf-8' 進(jìn)行轉(zhuǎn)換
u'你好世界'# 注意這不是錯(cuò)誤,這是 unicode 字符串
如果函數(shù)或類等對(duì)象接收的是 str 類型的字符串,但你傳的是 unicode,Python2 會(huì)默認(rèn)使用 ascii 將其編碼成 str 類型再運(yùn)算,這時(shí)就很容易出現(xiàn) UnicodeEncodeError。
讓我們看看例子:
>>>u_str=u'你好'
>>>str(u_str)
Traceback(most recent calllast):
File"
UnicodeEncodeError:'ascii'codeccan'tencode charactersinposition0-1:ordinalnotinrange(128)
在上面的代碼中,u_str 是一個(gè) unicode 類型的字符串,由于 str() 的參數(shù)只能是 str 類型,此時(shí) Python 會(huì)試圖使用 ascii 將其編碼成 ascii,也就是:
u_str.encode('ascii') // u_str 是 unicode 字符串
上面將 unicode 類型的中文使用 ascii 編碼轉(zhuǎn),肯定會(huì)出錯(cuò)。
再看一個(gè)使用 raw_input 的例子,注意 raw_input 只接收 str 類型的字符串:
>>>name=raw_input('input your name: ')
inputyourname:ethan
>>>name
'ethan'
>>>name=raw_input('輸入你的姓名:')
輸入你的姓名:小明
>>>name
'?°????'
>>>type(name)
>>>name=raw_input(u'輸入你的姓名: ')# 會(huì)試圖使用 u'輸入你的姓名'.encode('ascii')
Traceback(most recent calllast):
File"
UnicodeEncodeError:'ascii'codeccan't encode characters in position 0-5: ordinal not in range(128)
>>> name = raw_input(u'輸入你的姓名:'.encode('utf-8')) #可以,但此時(shí) name 不是 unicode 類型
輸入你的姓名: 小明
>>> name
'xe5xb0x8fxe6x98x8e'
>>> type(name)
>>> name = raw_input(u'輸入你的姓名:'.encode('utf-8')).decode('utf-8') # 推薦
輸入你的姓名:小明
>>> name
u'u5c0fu660e'
>>> type(name)
再看一個(gè)重定向的例子:
hello=u'你好'
printhello
將上面的代碼保存到文件 hello.py,在終端執(zhí)行 python hello.py 可以正常打印,但是如果將其重定向到文件 python hello.py > result 會(huì)發(fā)現(xiàn) UnicodeEncodeError。
這是因?yàn)椋狠敵龅娇刂婆_(tái)時(shí),print 使用的是控制臺(tái)的默認(rèn)編碼,而重定向到文件時(shí),print 就不知道使用什么編碼了,于是就使用了默認(rèn)編碼 ascii 導(dǎo)致出現(xiàn)編碼錯(cuò)誤。
應(yīng)該改成如下:
hello=u'你好'
printhello.encode('utf-8')
這樣執(zhí)行 python hello.py > result 就沒(méi)有問(wèn)題。
小結(jié)
UTF-8 是一種針對(duì) Unicode 的可變長(zhǎng)度字符編碼,它是 Unicode 的實(shí)現(xiàn)方式之一。
Unicode 字符集有多種編碼標(biāo)準(zhǔn),比如 UTF-8, UTF-7, UTF-16。
在進(jìn)行同時(shí)包含 str 類型和 unicode 類型的字符串操作時(shí),Python2 一律都把 str 解碼(decode)成 unicode 再運(yùn)算。
如果函數(shù)或類等對(duì)象接收的是 str 類型的字符串,但你傳的是 unicode,Python2 會(huì)默認(rèn)使用 ascii 將其編碼成 str 類型再運(yùn)算。
-
計(jì)算機(jī)
+關(guān)注
關(guān)注
19文章
7638瀏覽量
90373 -
編碼
+關(guān)注
關(guān)注
6文章
967瀏覽量
55596 -
字符
+關(guān)注
關(guān)注
0文章
235瀏覽量
25515 -
python
+關(guān)注
關(guān)注
56文章
4825瀏覽量
86375
原文標(biāo)題:Python字符編碼全解析
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
計(jì)算機(jī)中的電磁兼容情況
8086微型計(jì)算機(jī)常用字符編碼
什么是計(jì)算機(jī)系統(tǒng)、計(jì)算機(jī)硬件和計(jì)算機(jī)軟件?
在計(jì)算機(jī)中不同硬件對(duì)Hz的定義相同嗎
微型計(jì)算機(jī)中采用的邏輯元件是什么
基于嵌入式實(shí)時(shí)軟件在計(jì)算機(jī)中的應(yīng)用研究
計(jì)算機(jī)的數(shù)制及其轉(zhuǎn)換_微控制器原理及應(yīng)用
量子計(jì)算機(jī)的優(yōu)點(diǎn)_量子計(jì)算機(jī)的應(yīng)用_量子計(jì)算機(jī)的未來(lái)應(yīng)用
計(jì)算機(jī)中的圖像(Image)和圖形(Graphic)

計(jì)算機(jī)編碼全解析(中)

評(píng)論