bug:生产问题,Golang解决csv文件用excel打开中文乱码问题及常见编码和BOM头关系
bug:Golang解決csv文件用excel打開中文亂碼問題
1 場景及分析
場景:今天在生成csv文件之后,測試發(fā)現(xiàn)用office和wps打開亂碼
- 分析:經(jīng)過測試之后發(fā)現(xiàn)使用記事本打開不亂碼,同時用記事本打開之后另存為ANSI編碼之后用office和wps打開之后也不亂碼
- 由此可以斷定應(yīng)該是生成的csv文件缺少bom頭導(dǎo)致,office和wps無法斷定使用哪種編碼打開文件,最終產(chǎn)生中文亂碼問題
拓展:BOM頭
BOM(Byte Order Mark字節(jié)順序標記)是用來判斷文本文件是哪一種Unicode編碼的標記,其本身是一個Unicode字符(“\uFEFF”),位于文本文件頭部。 在不同的Unicode編碼中,對應(yīng)的bom的二進制字節(jié)也不同,因此在文件寫入的時候,我們通常根據(jù)BOM頭判斷是哪種編碼
2 解決
由此可以知道,是因為我們的文件沒有BOM頭導(dǎo)致中文亂碼,所以我們對癥下藥,直接寫入BOM頭即可
- writer.Write([]string{“\xEF\xBB\xBF”})
結(jié)果:
寫入之后,用十六進制查看
3 拓展:常見編碼和BOM頭
①中文編碼:
- gb2312 (采用兩個字節(jié)保存字符漢字,英文數(shù)字一個字節(jié))
- GBK (采用兩個字節(jié)保存字符漢字,英文數(shù)字一個字節(jié))
- GB18030 (英文數(shù)字都是一個字節(jié),中文是兩個或四個字節(jié))
- Unicode字符集(包含每個國家的所有字符)國際通用,unicode編碼 使用兩個字節(jié)—65536個字符,浪費空間為了節(jié)省空間使用轉(zhuǎn)碼形式
- utf-8 使用 1 、2、3個字節(jié) (EF BB BF 記事本添加的BOM(Byte Order Mark)頭,編碼的標記)
- utf-16 使用兩個字節(jié)—65536個字符 (FF FE 小端(尾) FE FF 大端(尾))
- utf-32 使用4個字節(jié)
- 臺灣 big5
- ANSI:在簡體中文Windows操作系統(tǒng)中, ANSI 編碼代表 GBK 編碼
②BOM頭(記事本特有的)BOM頭: Byte Order Mark
- 標識文件的編碼,實際大小比數(shù)據(jù)多3個字節(jié)
- 直接在記事本編輯數(shù)據(jù)保存,默認會給你的數(shù)據(jù)添加上BOM頭,使你的文件的大小比實際數(shù)據(jù)多3個字節(jié)(utf-8編碼)。但是,當你使用java程序往記事本寫入數(shù)據(jù)的時候,不會添加BOM頭
- 例如:當你用utf-8的格式編碼的時候,用程序去讀取文件,雖然顯示的數(shù)據(jù)是文件中保存的數(shù)據(jù),但是,可以用EditPlus打開程序編譯后的.class文件,并且轉(zhuǎn)化為16進制展示,你就會發(fā)現(xiàn),在前面的3個字節(jié)會是 :EF BB BF 這三個字節(jié)告訴記事本,這是一個用utf-8編碼的文件。
③分類
- utf-8 EF BB BF
- utf-16(Unicode) FF FE 編碼的時候,小的在后面(FE在后面) 小端 little endian
- utf-16(Unicode big endian) FE FF 編碼的時候,大的在后面(FF在后面) 大端 little endian
我用Notepad2新建個文本,寫上2個字: 我a
1.先轉(zhuǎn)成ANSI編碼:用Hex WorkShop打開 CE D2 61 (我:CE D2 , a:61H)
2.轉(zhuǎn)成Unicode編碼:(little-endian) FF FE 11 62 61 00 (我:6211H , a:0061H)
3.轉(zhuǎn)成Unicode編碼:(big-endian) FE FF 62 11 00 61
4.轉(zhuǎn)成UTF-8編碼: E6 88 91 61 (我:E68891H , a:61H)
5.轉(zhuǎn)成UTF-8編碼:(帶BOM) EF BB BF E6 88 91 61 (就多了個EF BB BF頭)
3.1 ANSI
(American National Standards Institute,美國國家標準學(xué)會)
ANSI編碼標準是指所有從基本ASCII碼基礎(chǔ)上發(fā)展起來的編碼標準,
比如擴展的ASCII碼(128~255占用)、GB2312、GBK、GB18030、BIG5等。每種編碼在ANSI標準中都為一頁,
比如encoding.gb2312頁代表GB2312字符集編碼
3.2 ASCII
(American Standard Code for Information Interchange,美國信息交換標準碼)碼
ANSI的ASCII字符集占一個字節(jié) ,8個位
起始占用: 0x00-0x7f(127個字符狀態(tài)) ,半角
擴充后全部占用: 0x00-0xff(共256個字符)
3.3 GB2312
常說的全角,使用2個字節(jié)編碼,共收錄了7445個字符,包括6763個漢字和682個其它符號
小于127的字符意義與原來相同,
當兩個大于127的字節(jié)連在一起,就表示一個漢字,
前面的一個字節(jié)(高字節(jié))從0xA1-0xF7,后面一個字節(jié)(低字節(jié))從0xA1-0xFE。
GB2312的兩個字節(jié)的最高位都是1,符合這個條件的碼位只有128*128=16384個
3.4 GBK
不再要求低字節(jié)一定小于127,只要第一個字節(jié)大于127,就認為是一個漢字的開始,
不管后面的字節(jié)是否小于127,都要和第一個字節(jié)組成一個兩字節(jié)的漢字.
GBK包含了GB2312的所有內(nèi)容,同時又增加了近20000個新的漢子(包括繁體字)和符號
3.5 BG18030
就是GBK的升級版,增加了很多字符,
中文Windows的缺省內(nèi)碼還是GBK,因為GB18030相對GBK增加的字符,
普通人是很難用到的
BG18030每個字可以由1個、2個或4個字節(jié)組成
單字節(jié):其值從0到0x7F。
雙字節(jié):第一個字節(jié)的值從0x81到0xFE,第二個字節(jié)的值從0x40到0xFE(不包括0x7F)
四字節(jié):第一個字節(jié)的值從0x81到0xFE,第二個字節(jié)的值從0x30到0x39,第三個字節(jié)從0x81到0xFE,第四個字節(jié)從0x30到0x39。
3.6 BIG5
是香港、臺灣繁體中文區(qū)的字符集編碼標準。由于是各自獨立完成編碼標準,所以最后互相不兼容。
從ASCII、GB2312、GBK到GB18030,,這些編碼方法是向前兼容的,即同一個字符在這些方案
中總是有相同的編碼,區(qū)分中文編碼的方法是高字節(jié)的最高位不為0。按照程序員的稱呼,
GB2312、GBK到GB18030和BIG5都屬于DBCS(double-byte charater set,雙字節(jié)字符集)
或者說MBCS(mutil-byte charater set,多字節(jié)字符集)
在DBCS雙字節(jié)字符集中,GB內(nèi)碼的存儲格式始終是big endian,即高位在前。
在讀取DBCS字符流時,只要遇到高位為1的字節(jié),就可以將下兩個字節(jié)作為一個雙字節(jié)編碼,
而不用管低字節(jié)的高位是什么。
3.7 Unicode
Unicode的學(xué)名是"Universal Multiple-Octet Coded Character Set",簡稱為UCS。
UCS可以看作是"Unicode Character Set"的縮寫。
ISO(International Organization for Standardization或International Standard Organized)國際標準化組織
廢除了所有地區(qū)性編碼方案,重新搞了一套可以包含地球上所有文化的文字和符號的編碼方案。
他們稱這個方案為Universal Multiple-Octet Coded Character Set(通用多8位編碼字符集),簡稱UCS
ISO直接規(guī)定必須用兩個字節(jié),也就是16位來統(tǒng)一表示所有的字符,對于ASCII里的那些“半角”字符,
UNICODE保持其原碼不變,只是將其由原來的8位擴展為16位,而其它文化和語言的字符則全部重新統(tǒng)一編碼。
由于“半角”英文符號只用到了低8位,所以其高8位永遠是0,會多浪費一倍的空間.
由于UNICODE設(shè)計初期的局限性(并沒有考慮到與現(xiàn)有編碼的兼容性),
所以使得UNICODE與GBK(GB18030、BG2312等)在排版上完全不一樣,
沒有一種簡單的算法可以把內(nèi)容從UNICODE編碼和兩一種編碼進行轉(zhuǎn)換,這種轉(zhuǎn)換必須通過查表來進行。
Unicode是2個字節(jié)的編碼,所以也稱UCS-2,如果幾百年后地球上的字符又多了很多的話,ISO已經(jīng)準備好了UCS-4方案了
也就是4個字節(jié)的編碼,而Unicode只與ASCII兼容(更準確地說,是與ISO-8859-1兼容),與GB碼不兼容。
例如“漢”字的Unicode編碼是6C49,而GB碼是BABA。
在非 Unicode 環(huán)境下,由于不同國家和地區(qū)采用的字符集不一致,很可能出現(xiàn)無法正常顯示所有字符的情況。
微軟公司使用了代碼頁(Codepage)轉(zhuǎn)換表的技術(shù)來過渡性的部分解決這一問題,
即通過指定的轉(zhuǎn)換表將非 Unicode 的字符編碼轉(zhuǎn)換為同一字符對應(yīng)的系統(tǒng)內(nèi)部使用的 Unicode 編碼。
可以在“語言與區(qū)域設(shè)置”中選擇一個代碼頁作為非Unicode編碼所采用的默認編碼方式,
如936為簡體中文GBK,950為正體中文Big5(皆指PC上使用的)。在這種情況下,
一些非英語的歐洲語言編寫的軟件和文檔很可能出現(xiàn)亂碼。而將代碼頁設(shè)置為相應(yīng)語言中文處理又會出現(xiàn)問題,
這一情況無法避免。從根本上說,完全采用統(tǒng)一編碼才是解決之道,但目前尚無法做到這一點。
代碼頁技術(shù)現(xiàn)在廣泛為各種平臺所采用。UTF-7(的代碼頁是65000,UTF-8 的代碼頁是65001。
3.8 UTF-8
任何文字在Unicode中都對應(yīng)一個值,這個值稱為代碼點code point.代碼點的值通常寫成U+ABCD的格式
而文字和代碼點之間的對應(yīng)關(guān)系就是UCS-2(Universal Character Set coded in 2 octets)
UCS-4,即用四個字節(jié)表示代碼點。
它的范圍為 U+00000000~U+7FFFFFFF,其中 U+00000000~U+0000FFFF和UCS-2是一樣的。
UCS-2和UCS-4只規(guī)定了代碼點和文字之間的對應(yīng)關(guān)系,并沒有規(guī)定代碼點在計算機中如何存儲。
規(guī)定存儲方式的稱為UTF(Unicode Transformation Format),其中應(yīng)用較多的就是UTF-16和UTF-8了
UTF是“UCS Transformation Format”的縮寫,
是"Unicode字符集轉(zhuǎn)換格式",是"怎么樣將Unicode定義的數(shù)字轉(zhuǎn)換成程序數(shù)據(jù)"
UTF-8以字節(jié)為單位對Unicode進行的特殊編碼。從Unicode到UTF-8的編碼方式如下:
Unicode編碼(16進制) ║ UTF-8 字節(jié)流(二進制)
000000 - 00007F ║ 0xxxxxxx
000080 - 0007FF ║ 110xxxxx 10xxxxxx
000800 - 00FFFF ║ 1110xxxx 10xxxxxx 10xxxxxx
010000 - 10FFFF ║ 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
UTF-8的特點是以字節(jié)為單位對Unicode進行編碼,對不同范圍的字符使用不同長度的編碼。
對于0x00-0x7F之間的字符,UTF-8編碼與ASCII編碼完全相同。UTF-8編碼的最大長度是4個字節(jié)。
從上表可以看出,4字節(jié)模板有21個x,即可以容納21位二進制數(shù)字。Unicode的最大碼位0x10FFFF也只有21位。
例1:“漢”字的Unicode編碼是0x6C49。0x6C49在0x0800-0xFFFF之間,使用3字節(jié)模板了 1110xxxx 10xxxxxx 10xxxxxx
將0x6C49寫成二進制是:0110 1100 0100 1001, 用這個比特流依次代替模板中的x,
得到:11100110 10110001 10001001,即E6 B1 89。
例2:Unicode編碼0x20C30在0x010000-0x10FFFF之間,使用用4字節(jié)模板了:
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。
將0x20C30寫成21位二進制數(shù)字(不足21位就在前面補0):0 0010 0000 1100 0011 0000,
用這個比特流依次代替模板中的x,
得到:11110000 10100000 10110000 10110000,即F0 A0 B0 B0。
UTF-8是ASCII的一個超集。因為一個純ASCII字符串也是一個合法的UTF-8字符串,所以現(xiàn)存的ASCII文本不需要轉(zhuǎn)換。
為傳統(tǒng)的擴展ASCII字符集設(shè)計的軟件通常可以不經(jīng)修改或很少修改就能與UTF-8一起使用。
使用標準的面向字節(jié)的排序例程對UTF-8排序?qū)a(chǎn)生與基于Unicode代碼點排序相同的結(jié)果。
(盡管這只有有限的有用性,因為在任何特定語言或文化下都不太可能有仍可接受的文字排列順序。)
UTF-8和UTF-16都是可擴展標記語言文檔的標準編碼。所有其它編碼都必須通過顯式或文本聲明來指定。
任何面向字節(jié)的字符串搜索算法都可以用于UTF-8的數(shù)據(jù)(只要輸入僅由完整的UTF-8字符組成)。
但是,對于包含字符記數(shù)的正則表達式或其它結(jié)構(gòu)必須小心。
3.9 UTF-16、UTF-32
- UTF-16編碼以16位無符號整數(shù)為單位,詳見百度google
- UTF-32編碼以32位無符號整數(shù)為單位,詳見百度google
字節(jié)序和BOM
① 字節(jié)序
PowerPC系列采用big endian方式存儲數(shù)據(jù),
而x86系列則采用little endian方式存儲數(shù)據(jù),
比如:0x12345678 雙字型數(shù)據(jù) ,占4個字節(jié)
低位數(shù)據(jù)----------------->高位數(shù)據(jù)
12 34 56 78 H
低地址------------------->高地址
0x01 0x02 0x03 0x04 內(nèi)存中
| 12 | 34 | 56 | 78 | big endian 方式
| 78 | 56 | 34 | 12 | little endian方式
little endian方式個人理解:
(起始地址存放高位數(shù)據(jù),左邊12是低數(shù)據(jù)位放在尾部,是低數(shù)據(jù)位,不是指二進制中的右邊的低數(shù)值位)
C/C++語言編寫的程序里數(shù)據(jù)存儲順序是跟編譯平臺所在的CPU相關(guān)的,
而java是跨平臺的,采用big endian方式來存儲數(shù)據(jù)
網(wǎng)絡(luò)字節(jié)序也是big endian方式
②BOM
BOM(byte-order mark)文件編碼頭,即 字節(jié)順序標記.
它是插入到以UTF-8、UTF16或UTF-32編碼文件開頭的特殊標記,
用來標記多字節(jié)編碼文件的編碼類型和字節(jié)順序(big-endian或little- endian)。
一般用來識別文件的編碼類型。
根據(jù)字節(jié)序的不同,UTF-16可以被實現(xiàn)為UTF-16LE或UTF-16BE,UTF-32可以被實現(xiàn)為UTF-32LE或UTF-32BE。
例如:
Unicode編碼 ║ UTF-16LE ║ UTF-16BE ║ UTF32-LE ║ UTF32-BE
0x006C49 ║ 49 6C ║ 6C 49 ║ 49 6C 00 00 ║ 00 00 6C 49
0x020C30 ║ 43 D8 30 DC ║ D8 43 DC 30 ║ 30 0C 02 00 ║ 00 02 0C 30
Unicode標準建議用BOM(ByteOrderMark)來區(qū)分字節(jié)序,
即在傳輸字節(jié)流前,先傳輸被作為BOM的字符"零寬無中斷空格"。
這個字符的編碼是FEFF,而反過來的FFFE(UTF-16)和FFFE0000(UTF-32)在Unicode中都是未定義的碼位,
不應(yīng)該出現(xiàn)在實際傳輸中。
BOM編碼頭 常見形式如下:
EF BB BF = UTF-8 (可選標記,因為Unicode標準未有建議)
FE FF = UTF-16, big-endian (大尾字節(jié)序標記)
FF FE = UTF-16, little-endian (小尾字節(jié)序標記) (也是windows中的Unicode編碼默認標記)
00 00 FE FF = UTF-32, big-endian (大尾字節(jié)序標記)
FF FE 00 00 = UTF-32, little-endian (小尾字節(jié)序標記)
對于UTF-8來說,BOM標記的有無并不是必須的,是可選的,因為UTF8字節(jié)沒有順序,不需要標記.
也就是說一個UTF-8文件可能有BOM,也可能沒有BOM.
微軟在自己的UTF-8格式的文本文件之前加上了EF BB BF三個字節(jié),
windows上面的notepad等程序就是根據(jù)這三個字節(jié)來確定一個文本文件是ASCII的還是UTF-8的,
然而這個只是微軟暗自作的標記, 其它平臺上不一定會對UTF-8文本文件做個這樣的標記。
微軟的一些軟件會做這種檢測,但有些軟件不做這種檢測, 而把它當作正常字符處理。(傳說中的亂碼問題)
再舉個例子
說的是Notepad2這個體積小,啟動速度快,功能強的輕量級文本編輯器,代碼高亮等,完全可以替代系統(tǒng)記事本
以前剛用Notepad2的時候,經(jīng)常在打開一個文本文件時顯示亂碼,點什么編碼轉(zhuǎn)換也沒用,
比如ViDown.exe維棠下載器程序目錄下的Readme.txt,打開就是亂碼,點擊"文件",“編碼"方式,看到的是Unicode,
ok.先關(guān)掉Readme.txt,用16進制編輯器比如Hex WorkShop打開后發(fā)現(xiàn)前2個字節(jié)是CF C2,這在GBK中的編碼是
下載的"下”,說明該Readme.txt編碼不是Unicode,而是屬于ANSI編碼,
那么避免亂碼就要對Notepad2設(shè)置下,點"文件",“編碼’,“默認”,在下拉菜單中找到ANSI936,(上面說過它就是GBK)
并勾上"跳過Unicode檢測”, 好了再打開Readme.txt就正常顯示中文了.
在"文件",“編碼’,下有"UTF-8"和"UTF-8包含簽名”,這2個有什么區(qū)別呢?
其中"UTF-8包含簽名",這一選項是將文件編碼格式轉(zhuǎn)換為UTF-8(包含BOM編碼頭),
翻譯成"包含簽名"就看不懂了…
③關(guān)系
參考:https://www.cnblogs.com/saxum/p/15775502.html
總結(jié)
以上是生活随笔為你收集整理的bug:生产问题,Golang解决csv文件用excel打开中文乱码问题及常见编码和BOM头关系的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 肘关节附属运动测试软件,肘关节功能锻炼方
- 下一篇: 整懵了,蚂蚁金服6面成功拿下offer涨