json mysql乱码问题_JSON数据乱码问题
背景
程序員一提到編碼應(yīng)該都不陌生,像gbk、utf-8、ascii等這些編碼更是經(jīng)常在用,但時(shí)不時(shí)也會(huì)出個(gè)亂碼問(wèn)題,解決這個(gè)問(wèn)題的方法大部分都是先google和baidu一下,最后可能在某個(gè)犄角旮旯里找到一點(diǎn)信息,然后就機(jī)械的按部就班的模仿下來(lái),結(jié)果問(wèn)題可能真就迎刃而解了,然后就草草了事,下回遇到相似的問(wèn)題,可能又是重復(fù)上面的過(guò)程。很少有人有耐心去花精力弄明白這寫(xiě)問(wèn)題的根本原因,以及解決這些問(wèn)題的原理是什么。這篇文章就是通過(guò)一個(gè)實(shí)際案例,試著去講清楚什么是編碼,亂碼又是怎么產(chǎn)生的,以及如何解決。該案例是從lua_cjson.c這個(gè)庫(kù)開(kāi)始的,對(duì)這個(gè)庫(kù)不熟悉也沒(méi)關(guān)系,也不需要熟悉它,我們只是借用它來(lái)說(shuō)明亂碼問(wèn)題,只需要跟著文章的思路走就可以。
前段時(shí)間同事在作一個(gè)新項(xiàng)目的時(shí)候用到了lua_cjson.c這個(gè)庫(kù)(以下簡(jiǎn)稱(chēng)cjson),將json串轉(zhuǎn)換成LUA本地的數(shù)據(jù)結(jié)構(gòu),但是在使用的過(guò)程中出現(xiàn)了中文亂碼問(wèn)題,奇怪的是只有那么幾個(gè)字是亂碼,這其中就包括”朶”字,其他字一切正常。經(jīng)了解json串用的是GBK編碼,那問(wèn)題就來(lái)了,為什么用gbk編碼會(huì)出現(xiàn)這個(gè)問(wèn)題,原因是什么?又應(yīng)該怎么解決這個(gè)問(wèn)題?
要解釋清楚這個(gè)問(wèn)題,首先我們來(lái)看看json串都有哪些要求。
JSON規(guī)范
json全稱(chēng)JavaScript Object Notion是結(jié)構(gòu)化數(shù)據(jù)序列化的一個(gè)文本,可以描述四種基本類(lèi)型(strings,numbers,booleans and null)和兩種結(jié)構(gòu)類(lèi)型(objects and arrays)。
RFC4627中有這樣一段話(huà)
A string is a sequence of zero or more Unicode characters.
字符串有零個(gè)或多個(gè)unicode字符序列組成.
在這里稍微解釋下什么是unicode字符。我們都知道ascii字符有字母、數(shù)字等,但是他收錄的字只有一百多個(gè)。比如漢字就不是ascii字符,但是unicode收錄了漢字,所以漢字可以是unicode字符。這里要說(shuō)明的是unicode字符其實(shí)就是一些符號(hào)。
現(xiàn)在另一個(gè)問(wèn)題出來(lái)了,在json文本中應(yīng)該怎么表示這些字符。
在規(guī)范的Encoding片段是這樣說(shuō)的
JSON text SHALL be encoded in Unicode. The default encoding is UTF-8。
JSON文本SHALL把unicode字符編碼。默認(rèn)使用utf-8編碼。
我們看到在這里用到了SHALL[RFC2119]這個(gè)關(guān)鍵字,也就是說(shuō)字符必須被編碼后才能作為JSON串使用。而且默認(rèn)使用utf-8編碼。
如何判斷使用的是那種unicode編碼呢?
Since the first two characters of a JSON text will always be ASCII characters[RFC0020],
it is possible to determine whether an octet stream is UTF-8、UTF-16(BE or LE), or
UTF-32(BE or LE)by looking at the pattern of nulls in the first four octets.
由于json文本的前兩個(gè)字符(注意這里說(shuō)的是字符,不是字節(jié))一定是ASCII字符,因此可以從一個(gè)字節(jié)
流的前四個(gè)字節(jié)(注意是字節(jié))中判斷出該字節(jié)流是UTF-8、UTF-16(BE or LE)、or UTF-32(BE or LE)編碼。
00 00 00 xx UTF-32BE? (u32編碼大端)
xx 00 00 00 UTF-32LE? (u32編碼小端)
00 xx 00 xx UTF-16BE? (u16編碼大端)
xx 00 xx 00 UTF-16LE? (u16編碼小端)
xx xx xx xx UTF-8 ? (utf-8編碼)
ps:
u32用32位的4字節(jié)整數(shù)表示一個(gè)字符;
u16用16位的2字節(jié)整數(shù)表示一個(gè)字符,如果2字節(jié)表示不了,就用連續(xù)兩個(gè)16位的2字節(jié)整
數(shù)表示,所以就會(huì)出現(xiàn)u16編碼中有4個(gè)字節(jié)表示一個(gè)字符的情況,和u32的四字節(jié)不一
樣的是,該字符在u16中的前兩個(gè)字節(jié)和后兩個(gè)字節(jié)之間不會(huì)有字序的問(wèn)題。
utf-8用多個(gè)8位的1字節(jié)序列來(lái)表示一個(gè)字符,所以沒(méi)有字序的問(wèn)題.
截止到現(xiàn)在我們沒(méi)有看到任何關(guān)于可以使用GBK編碼的信息,難道json文本就不能用gbk編碼嗎,如果真的不能用的話(huà),那為什么cjson不是把所有的gbk編碼解釋稱(chēng)亂碼,而是只有某幾個(gè)字是亂碼.
在規(guī)范中對(duì)json解析器有這樣一段描述:
A JSON parser transforms a JSON text into another representation.
A JSON parser MUST accept all texts that conform to the JSON grammar.
A JSON parser MAY accept non-JSON forms or extensions.
json解析器可以將一個(gè)json文本轉(zhuǎn)換成其他表示方式。
json解析器MUST接受所有符合json語(yǔ)法的文本.
json解析器MAY接受非json形式或擴(kuò)展的文本.
亂碼的原因
從規(guī)范對(duì)對(duì)解析器的描述可以看到,規(guī)范并沒(méi)有要求解析器必須對(duì)文本的編碼方式做校驗(yàn),而且解析器也可以有選擇的去接受非json形式的文本。
現(xiàn)在我們?cè)賮?lái)看看cjson解析器是如何做的,在cjson開(kāi)頭的注釋中說(shuō)了這么一句話(huà):
Invalid UTF-8 characters are not detected and will be passed untouched。
If required, UTF-8 error checking should be done outside this library。
發(fā)現(xiàn)無(wú)效的UTF-8編碼會(huì)直接放過(guò),如果有必要對(duì)UTF-8編碼的檢查應(yīng)該在該庫(kù)的之外。
說(shuō)的很清楚,對(duì)非utf8編碼直接放過(guò),不做任何檢查,所以用gbk編碼不符合規(guī)范,但又可以被解析的答案就出來(lái)了。那”朶”等這些字的亂碼問(wèn)題又是怎么回事? 我們現(xiàn)在看看cjson對(duì)規(guī)范中的另外兩個(gè)編碼utf16、utf32是如何做的,然后再說(shuō)亂碼問(wèn)題.
在cjson解析方法的開(kāi)始處是這么做的:
/* Detect Unicode other than UTF-8(see RFC 4627, Sec 3)
*
* CJSON can support any simple data type, hence only the first
* character is guaranteed to be ASCII (at worst:'"'). This is
* still enough to detect whether the wrong encoding is in use.
*/
if (json_len >=2 && (!json.data[0] || !json.data[1]))
luaL_error(1,"JSON parser does not support UTF-16 or UTF-32");
前面我們說(shuō)過(guò)一個(gè)json串的前兩個(gè)字符一定是ascii字符,也就是說(shuō)一個(gè)json串至少也的有兩個(gè)字節(jié).所以這段代碼首先判斷json串的長(zhǎng)度是不是大于等2,然后根據(jù)串的前兩個(gè)字節(jié)的值,是否有零來(lái)判斷該文本是否是非utf-8編碼。結(jié)果已經(jīng)看到了,人家不支持規(guī)范上說(shuō)的u16和u32編碼.
現(xiàn)在我們就來(lái)看看”朶”這個(gè)子是如何變成亂碼的,經(jīng)過(guò)對(duì)cjson源碼的分析得知,cjson在處理字節(jié)流的時(shí)候當(dāng)遇見(jiàn)’\’反斜杠時(shí)會(huì)猜測(cè)后一個(gè)字節(jié)應(yīng)該是要被轉(zhuǎn)義的字符,比如\b、\r之類(lèi)的字符,如果是就放行,如果不是,cjson就認(rèn)為這不是一個(gè)正確的json格式,就會(huì)把這個(gè)字節(jié)給干掉,所以本來(lái)用兩個(gè)字節(jié)表示的漢子就硬生生的給掰彎了。
那”朶”字跟’\’反斜杠又有什么關(guān)系? 查詢(xún)這兩字符在編碼中的表示得出:
“朶” 0x965C
“\” 0x5C
這樣我們就看到”朶”字的低位字節(jié)和”\”字符相同,都是0x5C,如果這時(shí)候”朶”字后邊不是b、r之類(lèi)的可以被轉(zhuǎn)移ascii字符,cjson就會(huì)把這個(gè)字節(jié)和緊跟其后的一個(gè)字節(jié)抹掉,所以亂碼就產(chǎn)生了。
那我們應(yīng)該怎么解決這個(gè)問(wèn)題,讓cjson可以順利的支持gbk編碼呢,首先我們看看gbk編碼是怎么回事,為什么會(huì)出現(xiàn)低位字節(jié)和ascii沖突的問(wèn)題.
GB_編碼系列
先來(lái)了解一下GB系列的編碼范圍問(wèn)題:
GB2312(1980)共收錄7445個(gè)字符,6763個(gè)漢字和682個(gè)其他字符。
每個(gè)漢字及符號(hào)用兩個(gè)字節(jié)表示,為了跟ascii兼容,處理程序使用EUC存儲(chǔ)方法。
漢字的編碼范圍
高字節(jié): 0xB0 – 0xF7,
低字節(jié): 0xA1 – 0xFE,
占用72*94=6768,0xD7FA – 0xD7FE未使用。
GBK共收錄21886個(gè)字符,采用一字節(jié)和雙字節(jié)編碼。
單字節(jié)表示范圍
8位: 0x0 – 0x7F
雙字節(jié)表示范圍
高字節(jié): 0x81 – 0xFE
低字節(jié): 0x40 – 0x7E、0x80 – 0xFE
GB18030收錄70244個(gè)漢字,采用1、2、4字節(jié)編碼。
單字節(jié)范圍
8位: 0x0 – 0x7F
雙字節(jié)范圍
高字節(jié): 0x81 – 0xFE
低字節(jié): 0x40 – 0xFE
四字節(jié)范圍
第一字節(jié):0x81 – 0xFE
第二字節(jié):0x30 – 0x39
第三字節(jié):0x81 – 0xFE
第四字節(jié):0x30 – 0x39
由于GB類(lèi)的編碼都是向下兼容的,這里就有一個(gè)問(wèn)題,因?yàn)镚B2312的兩個(gè)字節(jié)的高位都是1,符合這個(gè)條件的碼位只有128*128=16384個(gè)。GBK和GB18030都大于這個(gè)數(shù),所以為了兼容,我們從上面的編碼范圍看到,這兩個(gè)編碼都用到了低位字節(jié)的最高位可以為0的情況。
最終得出的結(jié)論就是,在GBK編碼中只要該字符是兩個(gè)字節(jié)表示,并且低位字節(jié)是0x5C的字符都會(huì)被cjson弄成亂碼.
解決方案:
1) 不要使用gbk編碼,將你的字符串轉(zhuǎn)換成utf-8編碼.
2) 對(duì)cjson源碼稍微做個(gè)改動(dòng),就是在每個(gè)字節(jié)到來(lái)之前先判斷該字節(jié)是否大于127,如果大于則將該字節(jié)個(gè)隨后的一個(gè)字節(jié)放過(guò),否則交給cjson去處理。
總結(jié)
以上是生活随笔為你收集整理的json mysql乱码问题_JSON数据乱码问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: mysql8.0.20忘记密码_mysq
- 下一篇: informix和mysql数据量_in