技術(shù)實(shí)踐|數據遷移中GBK轉UTF8字符集問(wèn)題分析
導語(yǔ):在國產(chǎn)化創(chuàng )新的大背景下,數據庫遷移項目逐漸增多,在數據庫遷移過(guò)程中,源數據庫和目標數據庫字符集有時(shí)會(huì )不同,這時(shí)如何進(jìn)行字符集轉換則成為了一個(gè)重要的問(wèn)題,同時(shí)在轉換過(guò)程中還需要確保數據的完整性和一致性。
字符集轉換算法是一個(gè)復雜的領(lǐng)域,因此各個(gè)操作系統和庫實(shí)現可能會(huì )有所不同。此外,一些特定的字符集轉換還可能會(huì )涉及更復雜的操作。例如字符替換、丟棄或使用替代字符表示無(wú)法轉換的字符等。因此,實(shí)際的字符集轉換結果可能會(huì )因使用的庫、操作系統版本以及具體的轉換需求而有所差異。
1. 字符集介紹
■ASCII:
ASCII(American Standard Code for Information Interchange)是一個(gè)基于拉丁字母的字符集編碼方案,使用7位(8位的擴展ASCII)來(lái)表示字符。
ASCII字符集包含了基本的拉丁字母、數字、標點(diǎn)符號和一些特殊控制字符,共計128個(gè)字符。
ASCII是一個(gè)較為簡(jiǎn)單和有限的字符集,主要適用于英語(yǔ)及其他使用基本拉丁字母的語(yǔ)言。
■ Latin-1:
Latin-1是一種拉丁字符集編碼方案,使用8位(一個(gè)字節)來(lái)表示每個(gè)字符。
Latin-1(ISO 8859-1)覆蓋了ASCII字符集的范圍,并擴展了一些額外的特殊字符和符號,包括重音符號、貨幣符號、擴展的拉丁字母等。
Latin-1適用于多種西歐語(yǔ)言,如英語(yǔ)、法語(yǔ)、德語(yǔ)、西班牙語(yǔ)等,能夠表示這些語(yǔ)言中常見(jiàn)的字符需求。
■ GBK:
GBK是一種中文字符集編碼,主要用于表示中文字符和標點(diǎn)符號。它是GB2312(國標2312)的擴展版本,支持更多的漢字字符。
GBK使用雙字節編碼,每個(gè)字符占用兩個(gè)字節。其中,ASCII字符的編碼與ASCII字符集兼容,非ASCII字符則使用兩個(gè)字節來(lái)表示。
GBK能夠表示包括繁體中文、簡(jiǎn)體中文在內的大部分中文字符。
■ UTF-8:
UTF-8是一種通用的字符集編碼,支持全球范圍內的幾乎所有字符,包括各種語(yǔ)言的文字、符號和表情符號。
UTF-8使用變長(cháng)編碼,根據字符的Unicode值,使用1到4個(gè)字節來(lái)表示字符。其中,ASCII字符使用一個(gè)字節表示,非ASCII字符使用多個(gè)字節表示。
UTF-8兼容ASCII字符集,可以表示所有ASCII字符,因此它是廣泛使用的字符集編碼方案。
2. 數據遷移背景介紹
早期的數據倉庫字符集一般都是GBK,而現在的數據倉庫都使用UTF8字符集,所以字符集轉換是遷移過(guò)程中最關(guān)鍵的一個(gè)步驟。正常情況下如果源數據庫沒(méi)有亂碼,那么字符集轉換不會(huì )出現問(wèn)題,GBK可以正常轉換為UTF8。但如果源數據庫有亂碼存在,那么在字符集轉換過(guò)程中就會(huì )出現很多不確定的問(wèn)題,而且不同的字符集轉換方式不同,結果也不同。
3. 字符集轉換方法介紹
目前字符集轉換采用兩種方式:
■ Linux系統的iconv
■ 編寫(xiě)程序實(shí)現字符集轉換,推薦使用Golang、Python、C,考慮到項目實(shí)施的可操作性和技術(shù)通用性,一般可以采用Python語(yǔ)言,且可以通過(guò)多線(xiàn)程提高轉碼效率。
● iconv
iconv是一個(gè)在Linux和其他類(lèi)Unix操作系統上廣泛使用的命令行工具。它用于進(jìn)行字符編碼之間的轉換。iconv的名稱(chēng)是“character set conversion”(字符集轉換)的縮寫(xiě)。
在Linux系統中,iconv命令使用的字符集轉換算法主要依賴(lài)于GNU C庫(GNU C Library,簡(jiǎn)稱(chēng)為glibc)提供的轉換功能。glibc是Linux系統的標準C庫,為許多基本操作提供了支持,包括字符集轉換。
glibc中的字符集轉換算法主要基于Unicode標準:Unicode是一種字符編碼標準,它為世界上幾乎所有的字符提供了唯一的編碼值。glibc使用Unicode標準作為內部字符表示,以實(shí)現不同字符集之間的轉換。
● Python的codecs模塊
codecs是Python標準庫中的一個(gè)模塊,用于字符編碼和解碼操作。它提供了一組函數和類(lèi),用于在不同的字符編碼之間進(jìn)行轉換。在處理文本數據時(shí),經(jīng)常需要將文本從一種編碼格式轉換為另一種編碼格式。這可能涉及到將文本從Unicode轉換為其他編碼(如UTF-8、ASCII等),或者將文本從其他編碼轉換為Unicode。codecs模塊提供了一種簡(jiǎn)單而一致的方式來(lái)執行這些編碼和解碼操作。
以下是codecs模塊的一些主要特性和功能:
編碼和解碼函數:codecs模塊提供了一組函數,如codecs.encode()和codecs.decode(),用于執行字符編碼和解碼操作。這些函數接受輸入文本和目標編碼格式作為參數,并返回編碼或解碼后的文本。
多種編碼支持:codecs模塊支持許多常見(jiàn)的字符編碼格式,包括ASCII、UTF-8、UTF-16、UTF-32等。它還提供了對其他編碼格式的支持,如Base64、Quoted-Printable、ROT13等。
錯誤處理:在進(jìn)行字符編碼和解碼時(shí),可能會(huì )出現無(wú)法處理的字符或編碼錯誤。codecs 模塊允許指定不同的錯誤處理策略,以處理這些錯誤情況。例如,可以選擇忽略無(wú)法處理的字符,替換它們或引發(fā)異常。
使用codecs模塊,可以便捷地進(jìn)行不同編碼之間的轉換,處理文本數據的編碼問(wèn)題,并確保數據在不同環(huán)境中正確地傳輸和解釋。
4. 項目實(shí)施中字符集轉換介紹
以TERADATA(TD)數據庫遷移到高斯數據庫為例,一般TD數據庫默認是使用latin1的字符集,而應用一般使用中文GBK字符集在TD數據庫中存儲數據,所以當從TD數據庫遷移到其他數據庫時(shí),應該以GBK字符集作為源數據庫字符集。
數據遷移主要流程如下:
■從TD數據庫中導出數據并以GBK字符集落地為數據文件。
■將GBK數據文件轉換為UTF8文件。
■將UTF8數據文件導入到高斯數據庫(高斯數據庫的外表加載也可以將GBK字符集轉換為UTF8字符集,在此不做討論)
某證券公司的業(yè)務(wù)表部分示例數據如下,從TD數據庫中導出的數據是GBK字符集,數據中有3個(gè)字段,字段分隔符為:||,數據的第三個(gè)字段是中文。在遷移過(guò)程中中文字段可能會(huì )存在亂碼,所以在使用不同的字符集轉換方式后其轉換的結果也會(huì )有所不同。
示例數據中第一行的第三個(gè)中文字段有亂碼,正確的數據如下:
G000A||10000||廣東省廣州市天河區天河北路437號
E000D||20000||上海市浦東新區來(lái)安路685號
Q000D||20000||山東省青島市嶗山區仙霞嶺路17~21號
第一行中文字段的GBK十六進(jìn)制編碼如下:
數據中“州”字的GBK編碼:D6 DD,但是實(shí)際的數據中由于某種原因造成D6丟失,由于GBK是雙字節編碼,所以DD和后面的字節(CA)重新組成了另一個(gè)漢字:菔,而以此類(lèi)推后面的漢字,每?jì)蓚€(gè)字節組成一個(gè)漢字,但B7 34在GBK編碼中不能組成漢字,34在GBK編碼中是:4,也正是“437號”中的“4”。
當使用iconv轉換此帶有亂碼的GBK文件時(shí),效果如下所示。
iconv系統內核版本、os版本、自身版本如下:
[root@imo tmp]# uname -r 3.10.0-514.el7.x86_64 [root@imo tmp]# cat /etc/redhat-release Red Hat Enterprise Linux Server release 7.3 (Maipo) [root@imo tmp]# iconv -V iconv (GNU libc) 2.17
轉換命令如下:
[root@imo tmp]# iconv -f gbk -t utf8 -c sec_acc_gbk.txt -o sec_acc_utf8.txt
所以經(jīng)過(guò)iconv轉換后,B7和34不能組成漢字,所以B7被丟棄,而實(shí)際的內容如下:
G000A||10000||廣東省廣菔刑旌憂(yōu)旌穎甭437號
E000D||20000||上海市浦東新區來(lái)安路685號
Q000D||20000||山東省青島市嶗山區仙霞嶺路17~21號
當python程序使用內置庫codecs進(jìn)行代碼轉換后,可以有2個(gè)參數選項errors='replace'和errors='ignore',‘replace’表示當出現亂碼后可以把亂碼替換成“?”,而'ignore'表示當出現亂碼后,會(huì )把亂碼丟棄(和iconv特性相同)。
當使用codecs做代碼轉換時(shí),使用'replace'參數,部分代碼如下:
codecs.open(fileGbkAPName, 'r', encoding='{0}'.format(gbkFileEncoding),errors='replace')
轉換后的結果如下:
G000A||10000||廣東省廣?菔刑旌憂(yōu)?天河北路437號
E000D||20000||上海市浦東新區來(lái)安路685號
Q000D||20000||山東省青島市嶗山區仙霞嶺路17~21號
當使用codecs做代碼轉換時(shí),使用'ignore'參數,部分代碼如下:
codecs.open(fileGbkAPName, 'r', encoding='{0}'.format(gbkFileEncoding),errors='ignore')
轉換后的結果如下:
G000A||10000||廣東省廣菔刑旌憂(yōu)天河北路437號
E000D||20000||上海市浦東新區來(lái)安路685號
Q000D||20000||山東省青島市嶗山區仙霞嶺路17~21號
5. 總結
■ iconv 2.17版本就是根據glibc庫進(jìn)行字符集轉換,不能轉換的就丟棄,且當文件中有半個(gè)字節丟失后,后面轉換的中文字符很可能是不準確的。如在本示例中,遇到亂碼后,最終轉換的字符為:“菔刑旌憂(yōu)旌穎甭437號”
■ Python的內置庫codecs對中文轉換時(shí)采用一種“轉換最多中文字符”的策略,所以codecs在本示例中,遇到亂碼后,最終轉換的字符為:“菔刑旌憂(yōu)天河北路437號”。
6. Python程序示例
# -*- coding: utf-8 -*- import codecs import sys ## 定義常量 fileGbkAPName="/DATA/GBK_FILES/sec_acc_gbk.txt" fileUtf8APName="/DATA/UTF8_FILES/sec_acc_utf8.txt" gbkFileEncoding='gbk' utf8FileEncoding='utf8' def main(): try: # open TD數據文件(使用codecs庫) gbkFileStream = codecs.open(fileGbkAPName, 'rb', encoding='{0}'.format(gbkFileEncoding),errors='replace') # gbkFileStream = codecs.open(fileGbkAPName, 'rb', encoding='{0}'.format(gbkFileEncoding),errors='ignore') except Exception as e : print("不能Open數據文件{0},報錯信息{1},程序異常退出!!".format(fileGbkAPName,e)) sys.exit(-1) tmpGbkCont = gbkFileStream.readlines() # 轉換為utf8字符 utf8FileStream= open(f'{fileUtf8APName}','w',encoding=f'{utf8FileEncoding}') for gbkLine in tmpGbkCont: utf8Line = gbkLine.encode('{0}'.format(utf8FileEncoding)).decode('{0}'.format(utf8FileEncoding)).split('\n')[0] print(utf8Line) # 寫(xiě)入utf8文件 utf8FileStream.write(utf8Line+'\n') gbkFileStream.close() utf8FileStream.close() if __name__ == '__main__': main() else: print("程序執行非法調用,異常退出??!") sys.exit(-1)
*博客內容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀(guān)點(diǎn),如有侵權請聯(lián)系工作人員刪除。