參考資料: java中的字符集編碼入門 java編譯器編碼和JVM編碼問題?
Unicode是一個字符集,就好像一個字典一樣,收錄了全世界的文字啥的,英文是charset;
ascii、gbk、utf-8、utf-16叫字符集編碼,英文是encoding,例如utf-8和utf-16編碼是Unicode字符集的實(shí)現(xiàn),規(guī)定了字符在計(jì)算機(jī)中的具體編碼規(guī)則,也就是二進(jìn)制到底是什么樣的,在計(jì)算機(jī)中的意思是,可以是在硬盤文件中,也可以是內(nèi)存里。
昨天在看阮一峰大俠的es6,突然看到了es6對string的擴(kuò)展,介紹了string.codepointat()之類的方法,還涉及到了編碼方式,瞬間又懵逼了,編碼這個玩意我看了一遍又一遍,每次都覺得懂了些,可下次再遇到編碼的問題還是一頭霧水,理解的還是太淺了??!
所以我就好奇,在java里面這個編碼是咋地個處理法?平時用idea coding的時候,文件都是用utf-8保存的,也就是說java源代碼是用utf-8進(jìn)行編碼的,如下圖。

那么javac在編譯.java文件的時候也要按照utf-8編碼方式進(jìn)行讀取,否則就有可能出現(xiàn)亂碼。
那javac為什么知道這個文件是utf-8的呢,它特么肯定知不道,因?yàn)槲募木幋a方式并不儲存在文件里,文件中存儲的是內(nèi)容的二進(jìn)制表示。正常來說,如果開一個bash,運(yùn)行javac Test.java,那么他使用的是操作系統(tǒng)默認(rèn)的編碼方式進(jìn)行讀取Test.java,對于windows中文系統(tǒng)來說一般是gbk,這個值在java代碼中可以用System.getPRoperty(“file.encoding”)獲取 。所以如果一個源文件的編碼方式是utf-8,然后你直接javac它,絕逼會報(bào)下面的錯誤(如果你是win的話~):
public class test { public static void main(String args[]){ char cchar = 'a'; System.out.format("%x",(short)cchar); }}
但是為啥我在idea里面寫完,然后在idea里面運(yùn)行,毛事沒有呢?因?yàn)閕dea給咱處理了,指定了讀取源文件的編碼方式,我估計(jì)它是這么指定的:javac -encoding utf-8 test.java(記不住的話可以javac -help查看一下),這樣的話System.getProperty(“file.encoding”)也會變成utf-8的。 編譯完成,生成的.class文件默認(rèn)應(yīng)該是utf-8編碼的。
這樣javac讀入.java并且生成.class文件算是清楚了(真的嗎- -),但是還有一點(diǎn)我特么特別好奇也特別生氣,就是如果源文件有語法錯誤,javac會將錯誤信息重定向到bash中(不知道這個說法對不對~),也就是javac的輸出,總是亂碼,我就想這個輸出到底是什么編碼的???
我是Win7,開的git bash,系統(tǒng)默認(rèn)編碼是gbk,.java源文件是utf-8,bash解析輸入并顯示在屏幕上時使用的編碼是utf-8。
然后我故意弄個語法錯誤,然后 javac -encoding utf-8 test.java 出現(xiàn)下面這個jb玩意(說雞不說吧,文明你我他~)

看來輸出錯誤信息的時候bash用utf-8解碼顯示有亂碼??!
把bash編碼換成gbk試試

然后就正常顯示了~

這說明啥呢?說明javac在把錯誤信息重定向到bash時,使用的操作系統(tǒng)默認(rèn)的編碼方式對數(shù)據(jù)轉(zhuǎn)換后進(jìn)行傳輸,這也理所應(yīng)當(dāng),因?yàn)檫@里是一個邊界,javac和操作系統(tǒng)打交道的邊界,不管你javac用啥編碼,如果你想把一些信息給操作系統(tǒng),然后呈現(xiàn)給用戶,那么你就必須尊重人家操作系統(tǒng)的編碼方式,并且對輸入數(shù)據(jù)進(jìn)行正確的編碼,要不操作系統(tǒng)怎么會正常顯示輸出呢?不過這個輸出到操作系統(tǒng)的編碼方式一開始我在System.getProperties()中沒找到,只找到了一個sun.jnu.encoding是gbk,然后網(wǎng)上一搜,這個屬性是影響文件名創(chuàng)建的編碼的- - 。
后來我又實(shí)驗(yàn)了一下,發(fā)現(xiàn)javac -enciding utf-8 test.java設(shè)置的編碼只是說明javac編譯器讀取.java文件時使用的編碼方式,影響不到System.getProperty(“file.encoding”)這個屬性。原來這個屬性是啟動jvm時可以設(shè)置的,默認(rèn)是操作系統(tǒng)的編碼方式。java -Dfile.encoding=xxx test 進(jìn)行設(shè)置,而且這個屬性就是java程序運(yùn)行時與操作系統(tǒng)打交道時使用的編碼方式!當(dāng)然這個屬性是影響不到j(luò)avac的,因?yàn)閖avac要在java命令之前運(yùn)行~~
java程序運(yùn)行起來之前的編碼就說到這里吧,接下來說說jvm中的編碼。
jvm內(nèi)部使用的字符集編碼為utf-16,也就是字符在內(nèi)存中的儲存方式為utf-16對,就是16不是8,- -。現(xiàn)在utf-8這么火這么流行這么普及為啥不用8呢?好像是之前sun被unicode聯(lián)盟坑了~具體咋回事可以左轉(zhuǎn)bi乎問訊- -。就比如說一個String吧 ,實(shí)際的內(nèi)容是存在String類中private final char value[]數(shù)組中的,這個char數(shù)組存的東東就是經(jīng)過utf-16編碼的數(shù)據(jù)!嗯,就是這樣。感覺有些懵逼,現(xiàn)在企業(yè)開發(fā)一般全站都特么用utf-8,這咋jvm你內(nèi)部自己用個utf-16呢,尷尬。用就用吧,我也沒辦法- -。一開始java用utf16,有個原因好像是它的編碼方式是定長的,就用倆字節(jié)表示字符,java正好可以用一個char表示一個字符,完美啊。但天有不測風(fēng)云,倆字節(jié)最多表示65536個字符,可全世界的語言里的字符不止這么多啊,光?漢語都不止這么多,于是人家utf-16又?jǐn)U充了,java表示我?也跟進(jìn)(被逼的),于是utf-16就用2字節(jié)或者4個字節(jié)表示字符了,跟?utf-8一樣也成了變長編碼了- -。不過我就想不明白了,如果有一串字節(jié),用它來表示一個字符串,那?怎么解析啊,到底是讀取2字節(jié)作為一個字符還是讀取4字節(jié)作為一個字符啊,咋?區(qū)分呢?其實(shí)這樣的,不知道是unicode聯(lián)盟還是sun(估計(jì)是unicode),整了一個規(guī)定,unicode碼空間U+0000到U+FFFF為BMP(Basic Multilingual Plane基本多語言面),U+10000之后的碼空間對應(yīng)補(bǔ)充字符,然后為了正確讀取2字節(jié)字符(也就是bmp中的)和4字節(jié)字符(補(bǔ)充字符),規(guī)定U+D800到U+DFFF在bmp中不對應(yīng)字符,讓補(bǔ)充字符使用這一段。兩個char 組成了surrogate pair,第一個char成為高代理部分(high-surrogates range uD900到uDBFF ,1024個),第二個char叫低代理部分,uDC00到uDFFF,也是1024個,1024*1024也就是1048576個補(bǔ)充字符,加上bmp65536-2048個字符,一共1112064個。我這里說的亂七八糟的,大家可以來這里看,這篇文章講的老帶勁了http://www.360doc.cn/article/9470897_205152817.html 。
上面扯了這么半天,其實(shí)涉及到一個代碼點(diǎn)(Code Point)和代碼單元(Code Unit)的概念問題.
(引用自上面網(wǎng)址) 代碼點(diǎn)(Code Point)就是指Unicode中為字符分配的編號,一個字符只占一個代碼點(diǎn),例如我們說到字符“漢”,它的代碼點(diǎn)是U+6C49.代碼單元(Code Unit)則是針對編碼方法而言,它指的是編碼方法中對一個字符編碼以后所占的最小存儲單元。例如UTF-8中,代碼單元是一個字節(jié),因?yàn)橐粋€字符可以被編碼為1個,2個或者3個4個字節(jié);在UTF-16中,代碼單元變成了兩個字節(jié)(就是一個char),因?yàn)橐粋€字符可以被編碼為1個或2個char(你找不到比一個char還小的UTF-16編碼的字符,嘿嘿)。說得再羅嗦一點(diǎn),一個字符,僅僅對應(yīng)一個代碼點(diǎn),但卻可能有多個代碼單元(即可能被編碼為2個char)。
java類庫中有的方法是跟代碼點(diǎn)打交道的,有的是跟代碼單元打交道的,java中的代碼點(diǎn)就是指的Unicode字符集的代碼點(diǎn)了,代碼單元自然指的是utf-16的代碼單元。比如String.length( )返回的就是utf-16代碼單元的數(shù)量,看以下源碼:
/** * Returns the length of this string. * The length is equal to the number of <a href="Character.html#unicode">Unicode * code units</a> in the string. * * @return the length of the sequence of characters represented by this * object. */ public int length() { return count; }也就是說,對于BMP中的字符來說,length可以代表字符的個數(shù),但對于含有補(bǔ)充字符的字符串來說,length就不能反映出字符串中含有字符的真實(shí)個數(shù)了:


這個古怪的漢字是補(bǔ)充字符,在utf-16編碼中用4個字節(jié),也就是兩個char來表示,看到了吧,length( )返回的是 6 哦~~ 所以在一些用戶注冊的時候判斷用戶名長度,直接用length判斷其實(shí)是有些小問題的,當(dāng)然正常人是不會用補(bǔ)充字符的漢字的- - 另外在京東 淘寶注冊的時候一個漢字被算作2個字符- -,而且不支持 ‘
’ 這種補(bǔ)充字符 - - 要真想求出字符串的代碼點(diǎn)的數(shù)量也就是我們正常人所理解的字符的數(shù)量,可以用str.codePointCount(0,str.length-1)方法。
再比如對于str.charAt( int index) 這種index也是指的代碼單元,比如返回index為0的char,也就是代碼單元。
再比如 Character.toChars(0x2F81A) 這個方法的參數(shù)為unicode的代碼點(diǎn)(code point),返回一個utf-16編碼的char數(shù)組。
再比如str.getBytes()返回一個byte數(shù)組,這個byte數(shù)組的編碼為操作系統(tǒng)默認(rèn)的編碼,也就是file.encoding對應(yīng)的編碼,這個方法也可以接受具體的編碼作為參數(shù)來生成對應(yīng)編碼的byte數(shù)組。
等等等等,String類和Character類中有很多很多方法涉及代碼點(diǎn)和代碼單元,有時間的時候可以閱讀以下源碼的注釋,了解一下。
新聞熱點(diǎn)
疑難解答
圖片精選