先說編碼
1.為什么需要編碼
- 計(jì)算機(jī)是英語國(guó)家做出來的玩意兒亏较,所以它只能認(rèn)識(shí)英文字母符號(hào)担败,阿拉伯?dāng)?shù)字等贯要,其他所有的語言要想在計(jì)算機(jī)中顯示就必須進(jìn)行編碼,翻譯冬念。而計(jì)算機(jī)中表示數(shù)據(jù)信息的最小單元為一個(gè)字節(jié)趁窃,即8個(gè)bit,可以表示0~255個(gè)字符急前,世界上的各種語言千奇百怪醒陆,需要多個(gè)字節(jié)來表示,多個(gè)字節(jié)表示的數(shù)據(jù)要存儲(chǔ)在計(jì)算機(jī)中或者要在網(wǎng)絡(luò)間傳輸必須轉(zhuǎn)化為計(jì)算機(jī)認(rèn)識(shí)的byte裆针,如何轉(zhuǎn)換刨摩,那就是編碼寺晌。
2.常見的編碼
- ascii碼
只占用一個(gè)字節(jié),第一位為0澡刹,用低7位來編碼呻征。
- ISO-88591系列(ISO-88591-1 到ISO-88591-15)
這玩意兒也只占用一個(gè)字節(jié),歐洲一些國(guó)家的字符用ascii沒法表示出來罢浇,所以把a(bǔ)scii編碼的首位也用來編碼陆赋。
- GB2312
可以表示漢字的編碼,兩個(gè)字節(jié)(兼容ascii碼單字節(jié)形式)己莺,能表示6000多個(gè)漢字(區(qū)位碼奏甫,高位字節(jié)存當(dāng)前漢字在哪個(gè)區(qū),低位字節(jié)存在區(qū)中第幾位)凌受,相對(duì)算是挺少的阵子。。胜蛉。
其實(shí)在GB2312編碼里挠进,并不是所有的字符都會(huì)用兩個(gè)字節(jié)來表示的。為了能清晰說明這個(gè)這個(gè)問題誊册,我用二進(jìn)制編碼來解釋一下领突。 首先,ASCII編碼雖然說是用一個(gè)字節(jié)來表示字符案怯,但是它其實(shí)只用了后7位君旦,第1位永遠(yuǎn)是0。它的編碼范圍嘲碱,從00000000到01111111金砍,都是以0開頭的。 而GB2312編碼麦锯,就是在ASCII編碼的基礎(chǔ)上進(jìn)行擴(kuò)充的恕稠,它規(guī)定了:ASCII的字符完整地包含在GB2312里,編碼不變扶欣,仍然是以0開頭鹅巍,用一個(gè)字節(jié)來表示一個(gè)字符;對(duì)于ASCII沒有的字符料祠,就用1開頭來區(qū)分骆捧,用兩個(gè)字節(jié)合起來表示一個(gè)字符。 這樣髓绽,在解碼的時(shí)候凑懂,遇到字節(jié)是以0開頭的,就知道這一個(gè)字節(jié)就表示了一個(gè)字符梧宫;遇到字節(jié)是以1開頭的接谨,就知道要加上下一個(gè)字節(jié)合起來表示一個(gè)字符摆碉。這樣就在GB2312中既把ASCII的字符包含了進(jìn)來,又能將它們區(qū)分出來脓豪,能達(dá)到兼容的效果了巷帝。
- GBK
也是兩個(gè)字節(jié),但是可以表示的漢字有幾萬個(gè)扫夜,完全是在gb2312的基礎(chǔ)上進(jìn)行擴(kuò)展楞泼,完全兼容GB2312編碼,所以基本可以忘記有GB2312這個(gè)編碼笤闯,直接使用GBK替代他就好堕阔。
tips:其實(shí)gbk和GB2312也算是變長(zhǎng)編碼,根據(jù)碼表查詢到碼位置大小來進(jìn)行判斷用一個(gè)字節(jié)還是雙字節(jié)颗味。
=====================
以下兩個(gè)unicode編碼的實(shí)現(xiàn)..
- UTF-16
tips:最最最高效的編碼
他的編碼理念是常見的普通字符都用兩個(gè)字節(jié)存儲(chǔ)超陆,而且不需要查碼表什么的,因?yàn)閡nicode規(guī)范里浦马,世界上任何一個(gè)符號(hào)时呀,字符都有一個(gè)唯一編碼對(duì)應(yīng),所以u(píng)tf-16直接將對(duì)應(yīng)的字符的unicode編碼分別放在兩個(gè)字節(jié)里即可晶默,單字節(jié)就能表示的字符它以高位補(bǔ)0方式存儲(chǔ)谨娜。(以不同的處理器可能會(huì)以高位在前或者高位在后的方式解析,所以編碼解碼時(shí)需要指定到底是高位在前還是低位在前)
對(duì)于UCS-4輔助平面內(nèi)的字符磺陡,采用四字節(jié)存儲(chǔ):
Unicode碼位值為2AEAB趴梢,減去0x10000得到1AEAB(二進(jìn)制值為0001 1010 1110 1010 1011),前10位加上D800得到D86B币他,后10位加上DC00得到DEAB坞靶。于是該字的UTF-16編碼值為D86BDEAB(該值為大端表示,小端為6BD8ABDE)圆丹。
- UTF-8
任何一個(gè)程序員都知道的屌屌的編碼,它對(duì)utf-16編碼的優(yōu)化:
1.變長(zhǎng)編碼躯喇,高位為0辫封,表示它是一個(gè)ascii編碼字符(ascii編碼本身就是首位為0,所以u(píng)tf-8完全兼容ascii編碼)廉丽,大于一個(gè)字節(jié)的編碼倦微,會(huì)在首位以連續(xù)的1的個(gè)數(shù)來標(biāo)識(shí)是幾個(gè)字節(jié)。后面字節(jié)的前兩位一律設(shè)為10正压。
2.變長(zhǎng)帶來的好處:相對(duì)節(jié)約空間欣福,安全(utf-16是每?jī)蓚€(gè)挨著的字節(jié)表示一個(gè)字符,如果在網(wǎng)絡(luò)上傳輸時(shí)丟失了某個(gè)字節(jié)焦履,那么該字節(jié)后面的所有編碼將會(huì)全部亂掉拓劝,而utf-8編碼將不會(huì)應(yīng)用后續(xù)的編碼)
uftf-8編碼示意:
Unicode符號(hào)范圍 | UTF-8編碼方式
(十六進(jìn)制) | (二進(jìn)制)
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
3.編碼選擇
- 首先雏逾,中文不能使用ascii或者iso系列,單字節(jié)編碼不可以表示中文
- 其次郑临,gbk和gb2312必須選gbk栖博,不用多說(現(xiàn)在已經(jīng)流行GB18030了)
- utf-8和utf-16之間:java虛擬機(jī)使用utf-16進(jìn)行編碼,追求編碼解碼速度(字節(jié)固定好查找)厢洞,字節(jié)只在機(jī)器本地的磁盤和內(nèi)存之間流動(dòng)仇让,不走網(wǎng)絡(luò),所以也不會(huì)丟失躺翻。 在web開發(fā)中utf-8是最佳最佳選擇丧叽,空間小,容錯(cuò)率高~~~
4.深入javaweb書上提到的關(guān)鍵點(diǎn)
看一段字符到底會(huì)占多少內(nèi)存公你,不是看string.length有多長(zhǎng)踊淳,而是要看其用的什么編碼,(作者提到他在做項(xiàng)目時(shí)對(duì)cookie做過各種壓縮省店,但是最終并木有任何成效)
java的string使用的編碼是unicode嚣崭,但是,當(dāng)string存在于內(nèi)存中時(shí)(也就是當(dāng)程序運(yùn)行時(shí)懦傍、你在代碼中用string類型的引用對(duì)它進(jìn)行操作時(shí)雹舀、也就是string沒有被存在文件中且也沒有在網(wǎng)絡(luò)中傳輸(序列化)時(shí)),是“只有編碼而沒有編碼格式的”粗俱,所以java程序中的任何String對(duì)象说榆,說它是gbk還是utf-8都是錯(cuò)的,gbk和utf-8是編碼格式而不是編碼寸认,String在內(nèi)存中不需要“編碼格式”(記住編碼格式是在存文件或序列化的時(shí)候使用的), 它只是一個(gè)unicode的字符串而已
作者:溫悅
鏈接:https://www.zhihu.com/question/20361462/answer/14899233
來源:知乎
著作權(quán)歸作者所有签财。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處偏塞。
5.書中的其他知識(shí)點(diǎn)(與本文的東西無關(guān))
http請(qǐng)求的編碼:
- 首先唱蒸,urlPath路徑上的字符一般是通過utf-8編碼,而querystring中則是使用gbk編碼(為什么url中的中文編碼后會(huì)有一大堆%灸叼,因?yàn)閡rl編碼規(guī)范規(guī)定轉(zhuǎn)換為16進(jìn)制表示后需要在每個(gè)16進(jìn)制位前加上%)神汹,<b>瀏覽器對(duì)url中path和query的編碼不一樣</b>
- tomcat對(duì)path的編碼定義<Connector URIEncoding="UTF-8"/>這里不定義將默認(rèn)采用iso編碼。
- 對(duì)于query的編碼解碼古今,是在后端代碼中調(diào)用request.getParameter("xx")時(shí)進(jìn)行的屁魏。解碼時(shí)會(huì)先查看http header的contenttype重定義的charset(需要在tomcat重配置<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true"/>)
tips:<b>在平時(shí)的開發(fā)中,盡量避免在uri或者path中傳中文捉腥,很容易亂碼氓拼,因?yàn)椴煌臑g覽器編碼規(guī)則不一樣,后端開發(fā)者沒辦法完全掌控</b> - 對(duì)于header的編碼解碼,沒地方可以配置桃漾,所以http header不支持傳入中文坏匪,一定要傳入中文需要先編碼。解碼觸發(fā)時(shí)機(jī)為request.getHeader("xx"),解碼方式固定以:iso
- 對(duì)于http postbody內(nèi)容呈队,瀏覽器端會(huì)先根據(jù)contentType設(shè)置的charset來進(jìn)行編碼剥槐,服務(wù)端接收到數(shù)據(jù)也將會(huì)以contentType中的charset進(jìn)行解碼。
utf-8再稍微多說一點(diǎn)
utf-8:https://en.wikipedia.org/wiki/UTF-8#Description
從上個(gè)小節(jié)得出結(jié)論宪摧,utf-8這種變長(zhǎng)的表示方式其實(shí)可以表示8個(gè)字節(jié)以內(nèi)的大小的編碼(因?yàn)樗且允鬃帜傅倪B續(xù)1的個(gè)數(shù)來判斷是幾個(gè)字節(jié)的)粒竖,其實(shí)utf-8在目前的最大可表示字節(jié)數(shù)為4.對(duì)應(yīng)如下:
數(shù)據(jù)庫的字符集utf8與utf8mb4
使用show variables like ‘%character%’;
可以查看mysql當(dāng)前的字符集。
utf8:標(biāo)準(zhǔn)的 UTF-8 字符集編碼是可以用 1~4 個(gè)字節(jié)去編碼21位字符几于,這幾乎包含了是世界上所有能看見的語言了蕊苗。然而在MySQL里實(shí)現(xiàn)的utf8最長(zhǎng)使用3個(gè)字節(jié),也就是只支持到了 Unicode 中的U+0000至U+FFFF)沿彭,包含了控制符朽砰、拉丁文,中喉刘、日瞧柔、韓等絕大多數(shù)國(guó)際字符。重要的事情說三遍:<b>特智能表示3個(gè)字節(jié)以內(nèi)的編碼字符</b>,<b>特智能表示3個(gè)字節(jié)以內(nèi)的編碼字符</b>,<b>特智能表示3個(gè)字節(jié)以內(nèi)的編碼字符</b>
utf8mb4:新版本的mysql才支持睦裳,簡(jiǎn)單說 utf8mb4 是 utf8 的超集并完全兼容utf8嘶炭,能夠用四個(gè)字節(jié)存儲(chǔ)更多的字符谓娃。(當(dāng)然這樣空間會(huì)比較浪費(fèi)一點(diǎn))
如果當(dāng)前db的字符集為utf8盐数,插入的數(shù)據(jù)為4個(gè)字節(jié)編碼時(shí)慨畸,將會(huì)拋出:incorrect string value
異常
nested exception is org.apache.ibatis.exceptions.PersistenceException: \n### Error updating database. Cause: ERR-CODE: [TDDL-4601][ERR_EXECUTOR] Incorrect string value: '\\xF2\\xBC\\xAF\\xBA\\xEF\\xBF...' for column 'config_value' at row 1 More: [http:\/\/middleware.alibaba-inc.com\/faq\/faqByFaqCode.html?faqCode=TDDL-4601]\n### The error may involve com.alibaba.cornerstone.dao.output.OutputAppConfigDAO.addBatch-Inline\n### The error occurred while setting parameters\n### SQL: insert into output_appconfig ( app_name, component_id, config_value, config_type, finished ) values ( ?, ?, ?, ?, ? ) , ( ?, ?, ?, ?, ? ) , ( ?, ?, ?, ?, ? )\n### Cause: ERR-CODE: [TDDL-4601][ERR_EXECUTOR] Incorrect string value: '\\xF2\\xBC\\xAF\\xBA\\xEF\\xBF...' for column 'config_value' at row 1
插入數(shù)據(jù)庫的數(shù)據(jù)是一堆mysql建表語句,為什么會(huì)出現(xiàn)4個(gè)字節(jié)編碼的字符
- 寫了個(gè)main方法蛛蒙,從磁盤讀入這個(gè)ddl.sql文件糙箍,挨個(gè)字符讀取并判斷其編碼字節(jié)數(shù),最終找到這樣一串字符(因?yàn)橹苯诱迟N過來就變樣了牵祟,所以截圖)
這里的第四個(gè)字符編碼為4位的(我本來還以為是ddl.sql文件里有四個(gè)字節(jié)的utf-8編碼才能表示的中文漢子呢~~~~~~~~~)深夯,原來是因?yàn)榻ū碚Z句的comment里有亂碼,而這些亂碼里有4字節(jié)utf-8字符诺苹。
- 為什么從公司內(nèi)部的db導(dǎo)出的建表與存在這樣奇怪的亂碼咕晋。(還沒搞清楚。筝尾。捡需。办桨。)
解決方式
- 修改數(shù)據(jù)庫字符集為utf8mb4筹淫,在java連接mysql時(shí)加上
set names utf8mb4;
,但是公司的dba很拽的,db字符集不支持自助修改损姜,dba整天整天的沒反應(yīng)饰剥。。摧阅。汰蓉。。蛋疼 - 在java端過濾掉這些4字節(jié)字符(這種亂碼本來就沒啥用棒卷,過濾掉豈不更好顾孽。。比规。~~)
正則表達(dá)式之unicode匹配
都知道正則表達(dá)式可以用[a-zA-Z]這樣的方式匹配字母若厚,那我們的中文字符也想這樣匹配怎么辦呢,用unicode匹配吧蜒什,世界上任何一個(gè)字符都可以用unicode來表示测秸。
-
\u
開頭表示直接匹配unicode編碼
2E80~33FFh:中日韓符號(hào)區(qū)。收容康熙字典部首灾常、中日韓輔助部首霎冯、注音符號(hào)、日本假名钞瀑、韓文音符沈撞,中日韓的符號(hào)、標(biāo)點(diǎn)仔戈、帶圈或帶括符文數(shù)字关串、月份,以及日本的假名組合监徘、單位晋修、年號(hào)、月份凰盔、日期墓卦、時(shí)間等。
3400~4DFFh:中日韓認(rèn)同表意文字?jǐn)U充A區(qū)户敬,總計(jì)收容6,582個(gè)中日韓漢字落剪。
4E00~9FFFh:中日韓認(rèn)同表意文字區(qū),總計(jì)收容20,902個(gè)中日韓漢字尿庐。
A000~A4FFh:彝族文字區(qū)忠怖,收容中國(guó)南方彝族文字和字根。
AC00~D7FFh:韓文拼音組合字區(qū)抄瑟,收容以韓文音符拼成的文字凡泣。
F900~FAFFh:中日韓兼容表意文字區(qū),總計(jì)收容302個(gè)中日韓漢字。
FB00~FFFDh:文字表現(xiàn)形式區(qū)鞋拟,收容組合拉丁文字骂维、希伯來文、阿拉伯文贺纲、中日韓直式標(biāo)點(diǎn)航闺、小符號(hào)、半角符號(hào)猴誊、全角符號(hào)等潦刃。
-
-p
表示匹配unicode編碼的屬性(unicode屬性有很多,類似中文標(biāo)點(diǎn)懈叹,漢字什么什么的)
\p{xx}
表示一個(gè)有屬性 xx 的字符,可以在左花括號(hào) { 后面增加 ^ 表示取反福铅。比如: \p{^Lu} 就等同于 \P{Lu}。
-
-P
表示匹配沒有unicode編碼的屬性
\P{xx}
表示一個(gè)沒有屬性 xx 的字符
最后项阴,用unicode正則表達(dá)式替換掉所有4個(gè)字節(jié)的utf-8編碼字符
前面講過utf-8的3個(gè)字節(jié)以內(nèi)能表示的字符的unicode編碼范圍滑黔,所以直接過濾掉不在這個(gè)范圍的字符
public static final String filterCodeLargerThan3Byte(String s) {
if (s == null) {
return s;
}
return s.replaceAll("[^\\u0000-\\u007F\\u0080-\\u07FF\\u0800-\\uFFFF]", "");
}