Python: 關於Unicode 的BOM - 傑克! 真是太神奇了! - 痞客邦

文章推薦指數: 80 %
投票人數:10人

註一: 主要是因為可使用的編碼數只有256 個, 而不同code page 之間會對應不同的符號, 進而無法得知資訊的原始樣貌. 關於Unicode 的BOM (Byte Order Mark) ... 傑克!真是太神奇了! 跳到主文 記性不好,寫程式,架主機...都需要看小抄! 歡迎光臨MagicJack在痞客邦的'小抄'天地 部落格全站分類:數位生活 相簿 部落格 留言 名片 公告版位 從小害怕寫作文,文筆不佳到現在,還請各位讀者大大: 1.發現有錯誤,請留言告知.(或者你'覺得'不對也行) 2.用字措辭不當,請留言告知. 3.有看沒有懂?幫到忙也好,幫倒忙也罷,總之留個言吧. Dec16Thu202111:32 Python:關於Unicode的BOM Unicode的誕生背景 最早電腦使用的字元編碼都是ASCII.ASCII作為美國的計算機編碼標準,理所當然的只包含了英語的26字母大小寫,再加上一些常用符號.後來因應一些非英語系國家/地區需要,IBM將ASCII編碼擴充加入他們各自所需要的一些特殊字元,是為codepage.例如:codepage437是原始的英文頁碼;codepage858是帶有歐元符號的多語言頁碼(我們用的正體中文是codepage950).所以DOS,Windows作業系統上有chcp指令可以查訽/切換不同的codepage. 但是這只解決了區域性的交流問題.國際交流上的問題依然存在(註一).例如:亞洲語系的問題,CJK字元編碼統一的問題...於是有了Unicode聯盟,致力於制定標準,並努力的將全球各種文字符號的編碼統一,以利於資訊的交流. 註一:主要是因為可使用的編碼數只有256個,而不同codepage之間會對應不同的符號,進而無法得知資訊的原始樣貌. 關於Unicode的BOM(ByteOrderMark) BOM(ByteOrderMark)字面意思是位元組順序記號(註二,註三).那為什麼Unicode會有BOM的定義?又為什麼需要BOM呢? 這是因為外部存儲體(RAM,ROM,flash...)的一直是以8位元為單位來定址,但現在的CPU早就已經不再只是8位元的,而是16位元,32位元甚至是64位元CPU了.這些大於8位元的CPU對於大於8位元的資料如何擺放在外部儲存體中卻有二種不同的主要流派: 小端序(Little-Endian):將資料依其位元組的數量級由小至大擺放在外部儲存體地址也是由小至大. 大端序(Big-Endian):將資料依其位元組的數量級由小至大擺放在外部儲存體地址卻是反過來由大至小. 這二大Endian各自都有支持的理由.不過幾十年下來,目前除了TCP/IP是Big-Endian之外,許多(軟體/硬體)協定都徧向採用Little-Endian(註四).Unicode不像TCP/IP或者其他協定只能用規定的endian進行資料交換,而是設計了BOM,讓各個系統可以個自以最方便的方法使用Unicode,但又可以解決不同endian系統之間的資訊交換. Unicode基本編碼為16bits大小,後來擴充為32bits(目前尚未流行)Unicode的編碼空間從U+0000到U+10FFFF,共有1,112,064個碼位(codepoint).為了讓資料可以順利在不同Endian取向的CPU之間交換,Unicode編碼的檔案最前頭先寫入一個數值為65,279(16bits格式:0xFEFF或32bits格式:0x0000FEFF)的BOM(ByteOrderMark). 要注意的是:端序問題只是資料進出(I/O)時的問題.在CPU內部不論是你是大端序機器或是小端序機器,UnicodeBOM的數值固定為65,279:16bits格式0xFEFF;32bits格式0x0000FEFF. 所以,以16bits格式為例:0xFEFF的MSB(最高數量級位元組)為0xFE,LSB(最低數量級位元組)為0xFF.想當然的小端序的機器將UTF-16BOM寫到檔案時,第一個位元組是LSB0xFF,此即為UTF-16-LE(小端序)編碼.反之大端序的機器將UTF-16BOM寫到檔案時,第一個位元組是MSB0xFE,所以就變成UTF-16-BE(大端序)編碼.這樣子資料檔案要在各種電腦之間交換時,就可以判定資料是不是和本機的Endian相同.如果不同就需要將資料的高低位元組交換一下,才能正確解碼. 不同Unicode編碼的BOM 編碼十六進位表示 UTF-8EFBBBF UTF-16-LEFFFE UTF-16-BEFEFF UTF-32-LEFFFE0000 UTF-32-BE0000FEFF UTF-8的編碼規則和BOM 至於UTF-8編碼:是將Unicode編碼的字串資料轉成8位元序列(轉換規則如下表:UTF-8編碼規則),所有機器都必需一個位元組接著一個位元組的讀(地址由小到大),所以不存在大小端的問題.還有為何UTF-8的BOM會變成EFBBBF.其實它依然是數值65,279(0xFEFF)編碼成UTF-8的結果.並不是什麼特別的設定. UTF-16BOM與UTF-8BOM互轉 UTF-16十六進位值FEFF 二進位值1111111011111111 UTF-8二進位值111011111011101110111111 十六進位值EFBBBF UTF-8編碼規則 CodePointRangebitsByte1Byte2Byte3Byte4Byte5Byte6   U+0000~  U+007F7 0xxxxxxx00~7F   U+0080~  U+07FF11 110xxxxxC0~DF 10xxxxxx80~BF   U+0800~  U+FFFF16 1110xxxxE0~EF 10xxxxxx80~BF 10xxxxxx80~BF  U+10000~ U+1FFFFF21 11110xxxF0~F7 10xxxxxx80~BF 10xxxxxx80~BF 10xxxxxx80~BF  U+200000~ U+3FFFFFF26 111110xxF8~FB 10xxxxxx80~BF 10xxxxxx80~BF 10xxxxxx80~BF 10xxxxxx80~BF U+4000000~U+7FFFFFFF31 1111110xFC~FD 10xxxxxx80~BF 10xxxxxx80~BF 10xxxxxx80~BF 10xxxxxx80~BF 10xxxxxx80~BF Unicode基本頁面(BasicMultilingualPlane,0x0~0xFFFF)的字符轉UTF-8可以3bytes以內完成. 輔助頁面(SupplementaryPlanes,0x10000~0x10FFFF)的字符才會動用到4bytes編碼. 5~6byte編碼基本上目前用不上. 註二:位元組順序記號這個字面翻譯有點小拗口,個人認為改稱之為端序記號似乎好些.你認為呢? 註三:你可能會看到有些簡体中文的網站把它翻譯料表,物料清單或材料清單,這應該是機器翻譯把它誤認為另一個BOM(BillOfMaterial).不要說我黑白講,這幾個例子就是:https://tech-wiki.online/cn/javascript-unicode.html(誤值為物料清单字符),https://cloud.tencent.com/developer/section/1125377(誤值為物料清单和材料清单) 註四:參看維基百科位元組順序. UTF-16的編碼規則 由於Unicode的編碼空間從U+0000到U+10FFFF,共有1,112,064個碼位(codepoint),超出16位元可以表示的範圍.因此UTF-16將輔助平面字符(SupplementaryPlanes),即超出0xFFFF的部份0x10000~0x10FFFF,以一對(二組)16位元資料來表示:第一組16位元編碼範圍0xD800~0xDBFF,加上第二組16位元編碼範圍0xDC00~0xDFFF此即所謂代理對(SurrogatePair). 為此Unicode也將區段0xD800~0xDFFF空下來不編碼. 代理對(SurrogatePair)的編碼方法如下: 將Unicode輔助平面字符的碼位(codepoint)減去0x10000,得到的數值範圍為20位元,並將之分為2組10位元. (0x10000~0x10FFFF)-0x10000=0x0~0xFFFFF 高位的10位元的值(範圍為0x0~0x3FF)加上0xD800得到第一個碼元,稱作前導代理(leadsurrogate). (0x0~0x3FF)+0xD800=0xD800~0xDBFF 低位的10位元的值(範圍為0x0~0x3FF)加上0xDC00得到第二個碼元,稱作後尾代理(trailsurrogate) (0x0~0x3FF)+0xDC00=0xDC00~0xDFFF UTF-16編碼規則 lead\trail 0xDC00 0xDC01 ... 0xDFFF 0xD8000x100000x10001…0x103FF 0xD8010x104000x10401…0x107FF 0xD8020x108000x10801…0x10BFF ⋮⋮⋮⋱⋮ 0xDBFF0x10FC000x10FC01…0x10FFFF Python如何處理BOM Python3在開啟檔案時(open())指定encoding參數可以讓我們選擇保留BOM(自己處理BOM的讀寫);或者也可以輕鬆點,讓系統自動處理BOM. 下列是我目前試過的Unicode相關的合法encoding參數值:(註五) 'utf-8':系統不自動處理BOM.(Python3預設值(註六)) 讀檔時BOM被當作資料讀入. 寫檔時,要依據需求自己先寫入一個BOM(write('\ufeff')).如果寫檔時沒有寫BOM,檔案的開頭自然也就不會有BOM(即所謂的UTF-8noBOM編碼). 'utf-8-sig':系統自動處理BOM. 讀檔時,自動跳過BOM(有沒有BOM都OK) 寫檔時,自動加BOM(一定有) 'utf-16':系統自動處理BOM. 讀檔時,自動跳過BOM(有沒有BOM都OK) 寫檔時,自動加BOM(一定有,大小端序應該是依系統而定) 'utf-16-le':系統不自動處理BOM. 讀檔時BOM被當作資料讀入. 寫檔時,依據需求自己先寫入一個BOM(write('\ufeff')). 讀/寫時,Python字串以Little-Endian解/編碼. 'utf-16-be':系統不自動處理BOM. 讀檔時BOM被當作資料讀入. 寫檔時,依據需求自己先寫入一個BOM(write('\ufeff')). 讀/寫時,Python字串以Big-Endian解/編碼. 注意:有系統自動處理BOM功能的是:'utf-8-sig'和'utf-16',不要弄混了. 註五:當然,還有utf-32相關的三個'utf-32','utf-32-le','utf-32-be'合法設定值,但是我自己沒試過,可能要大家自行腦補一下. 註六:這個預設值在Windows平台,有點不同.它預設是使用的頁碼950(Windows繁體中文版系統預設值).我們需要自行在環境變數(系統或者使用者均可)新增一個環境變數PYTHONUTF8=1;或者使用python-xutf8來啟動python(即加上參數-xutf8).如此Python3才會將encoding參數預設改為'UTF-8'(即encoding='UTF-8'). 另外,使用chcp65001指令並無法更動Python3預設的encoding設定. 不過有一個設定可以將你的命令提示字元,預設為使用65001頁碼;同時也會將Python3的encoding參數預設改為'UTF-8'.操作如下:打開控制台(如果沒有它的捷徑,在左下方的'搜尋欄'中輸入'控制台'也可以找到它),打開其中的'地區'設定.在'系統管理'頁籤中,點選下方的'變更系統地區設定(C)...'按鈕,開啟'地區設定'子視窗,並勾選'Beta:使用UnicodeUTF-8提供全語言支援(U)'.按下'確定'按鈕,然後重新開機即可. 控制台(請選擇'地區') 地區-系統管理設定畫面(點選'變更系統地區設定(C)...'按鈕) 地區設定子視窗(請勾選'Beta:使用UnicodeUTF-8提供全語言支援(U)') Python3實例 廢話不多說,來看幾個實例吧.下面這個很直觀吧!? xf3=open('./test-utf8.txt','w',encoding='utf-8') xf3.write(u'\uFEFF') xf3.write('UTF-8寫檔測試,自行加入utf-8BOM.\n') xf3.close() 輸出檔案的內容如下: python寫檔附加utf-8BOM 再來一個讀csv檔的例子.csv檔一般並沒有要求使用UnicodeBOM,但是MSExcel輸出的csv檔有BOM,讀檔時也要有BOM.下面這個例子用'utf-8-sig'來省略utf-8BOM的檢查(第12~13行).程式如下: importcsv importnumpyasnp defdict_csvloader(path): ''' Loadlinesintextfileasadict(). ''' keyDict=dict() withopen(path,newline='',encoding='utf-8-sig')ascsvfile: reader=csv.DictReader(csvfile) headers=list(next(reader,None).keys()) #ifheaders[0][0]==u'\ufeff': #headers[0]=headers[0][1:] forrowinreader: data=tuple(row.values()) ifdata[2]=='': break vstr=np.array(data[3:]) vstr[vstr=='']='0' keyDict[data[2]]=vstr.astype(np.float32) returnkeyDict,headers nutriTbl,headers=dict_csvloader('./nutrition.csv') 資料檔nutrition.csv範例如下: 樣品編號,食品分類,樣品名稱,修正熱量(kcal),水分(g),粗蛋白(g),粗脂肪(g),總碳水化合物(g),膳食纖維(g),鈉(mg),鉀(mg),鈣(mg),鎂(mg),鐵(mg),鋅(mg),維生素A總量(IU),維生素E總量(mg),維生素B1(mg),維生素B2(mg),菸鹼素(mg),維生素B6(mg),維生素C(mg) A0100101,穀物類,大麥仁,343,12.3,8.9,1.6,76.1,8.9,14,229,26,49,1.6,1.2,0,1.11,0.16,0.04,3.31,0.22,0.2 A0110101,穀物類,大麥片,352,12.1,8.6,1.8,76.7,6.0,7,246,13,55,2.2,0.8,0,0.26,0.15,0.04,3.23,0.18,9.8 A0120101,穀物類,大麥仁粉,374,5.7,7.1,1.3,85.0,7.2,10,307,25,50,1.1,1.3,0,0.12,0.09,0.03,2.85,0.23,0.0 追加註記 要是你不嫌麻煩,或者有點OO的潔癖:覺得直接用'\uFEFF'代表UnicodeBOM不對或者不夠道地.那可以考慮一下使用importcodecs引入codecs模組來使用下列常數: codecs.BOM codecs.BOM_BE codecs.BOM_LE codecs.BOM_UTF8 codecs.BOM_UTF16 codecs.BOM_UTF16_BE codecs.BOM_UTF16_LE codecs.BOM_UTF32 codecs.BOM_UTF32_BE codecs.BOM_UTF32_LE 參考連結 我的貼文:Python:.py檔的編碼問題 文章標籤 unicode BOM unicodeBOM utf-8BOM endian bigendian littleendian 端序 大端序 小端序 encoding='UTF-8' pythonencoding預設值 PYTHON環境變數 全站熱搜 創作者介紹 MagicJackTing 傑克!真是太神奇了! MagicJackTing發表在痞客邦留言(0)人氣() E-mail轉寄 全站分類:數位生活個人分類:python此分類上一篇:Python:.py檔的編碼問題 上一篇:Python:.py檔的編碼問題 下一篇:CSS:關於pseudo-class:not() ▲top 留言列表 發表留言 月曆 « 十月2022 » 日 一 二 三 四 五 六             1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31           熱門文章 最新文章 文章搜尋 文章分類 程式(4) python(7)git(3)Java(0)CLanguage(15) 嵌入式系統(6) KeilARMC(2)Arduino(3)KeilC51(2)GCC(3)OS(EmbeddedSystem)(3)硬體(9) 網頁(3) JavaScript(4)CSS(14)HTML(3) Windows(10)其他(6)部落格設定(6) 最新留言 新聞交換(RSS) 誰來我家 我的連結 QRCode POWEREDBY (登入) 回到頁首 回到主文 免費註冊 客服中心 痞客邦首頁 ©2003-2022PIXNET 關閉視窗 PIXNET Facebook Yahoo! Google MSN {{guestName}} (登出) 您尚未登入,將以訪客身份留言。

亦可以上方服務帳號登入留言 請輸入暱稱(最多顯示6個中文字元) 請輸入標題(最多顯示9個中文字元) 請輸入內容(最多140個中文字元) 請輸入左方認證碼: 看不懂,換張圖 請輸入驗證碼 送出留言



請為這篇文章評分?