Unicode
Unicode編碼定義了這個世界上幾乎所有字符(就是你眼睛看的字符比如ABC,漢字等)的數(shù)字表示,而且Unicode還兼容了很多老版本的編碼規(guī)范,例如你熟悉的 ASCII碼。
碼點
我們國家的每一個人都對應(yīng)唯一的一個身份證號,而Unicode也為了每個字符發(fā)了一張身份證,這張“身份證”上有一串唯一的數(shù)字ID確定了這個字符。
這串?dāng)?shù)字在整個計算機的世界具有唯一性,Unicode給這串?dāng)?shù)字ID起了個名字叫[碼點]。
碼點是如何表示?
U+XXXXXX 是碼點的表示形式,X 代表一個十六制數(shù)字,可以有 4-6 位,不足 4 位前補 0 補足 4 位,超過則按是幾位就是幾位。
字符A的ASCII碼是眾所周知是65吧,將65轉(zhuǎn)換成16進制就是41(16×4+(16^0)×1 = 65),按照規(guī)則前面補0,那么字符A的碼點表示就是U+0041,依次類推B的碼點表示就是U+0042...等等,漢字"你"的字符表示是“U+4F60”...
注意
-
unicode是一個碼表,描述的是字符和碼的對應(yīng)關(guān)系,整個unicode碼表是很大的一個返回,不只是兩個字節(jié),三個字節(jié),它可以有任意的長度,只是一個映射,相當(dāng)于一個字符到數(shù)字的大map
image.png
image.png
https://www.qqxiuzi.cn/zh/hanzi-unicode-bianma.php 至于存儲的時候占用幾個字節(jié),就是UTF-8或者UFT-16的事兒了。比如漢字 你 的unicode是\u4f60 存儲的時候,utf-8需要三個字節(jié),而并不是按碼表用兩個字節(jié)存。
漢字轉(zhuǎn)unicode 首先java內(nèi)存中所有的漢字都是unicode的,并用utf-16存儲的。所以String的toByte方法,得到數(shù)字,就是utf-16編碼。 注意注意,這里和utf-8沒任何關(guān)系。所以
Integer.toHexString('你')
得到的結(jié)果就是\u4f60。再次強調(diào),這個是utf-16編碼,和utf8沒有任何關(guān)系。
-
對于生僻字,比如“??”,在unicode碼表中是屬于20000-2A6D6這個段的。但是java中一個char是兩個字節(jié)的,表示不了這個碼了? 所以idea中會報錯:
image.png 但是String可以保存,所以String a = “??” , a.length() ==2 不是1 。這時候utf-16就是用兩個字節(jié)表示這個生僻的字符。
所以下面這個程序輸出兩個字節(jié),注意這里不是unicode碼表中的數(shù)字,而是utf-16存儲的unicode字符。我們所謂的漢字轉(zhuǎn)unicode,其實就是漢字轉(zhuǎn)utf-16.
-
char='恁',輸出他的unicode是\u6041,兩個字節(jié),utf-16沒問題。但是String a =“恁”
a.getBytes().length卻等于3個字節(jié)? 這是為啥? 因為getByte相當(dāng)于做了一次編碼,用的是操作系統(tǒng)默認(rèn)編碼,如果是utf-8,那么就是三個字節(jié)了??聪旅妫?/p>
image.png
默認(rèn)utf-8的getByte輸出字符長度是3,因為占用3個字節(jié)。
改成utf-16變成4了? 而且最后兩個字節(jié)79,96. 正好就是他的unicode碼\u4f60。
但是前兩個字節(jié)是什么?前面的feff是什么東西呢?
- 在wiki上我們可以看到:
UTF-16的大尾序和小尾序存儲形式都在用。一般來說,以Macintosh制作或存儲的文字使用大尾序格式,以Microsoft或Linux制作或存儲的文字使用小尾序格式。
為了弄清楚UTF-16文件的大小尾序,在UTF-16文件的開首,都會放置一個U+FEFF字符作為Byte Order Mark(UTF-16LE以FF FE代表,UTF-16BE以FE FF代表),以顯示這個文本文件是以UTF-16編碼,其中U+FEFF字符在UNICODE中代表的意義是ZERO WIDTH NO-BREAK SPACE,顧名思義,它是個沒有寬度也沒有斷字的空白。
原來FE FF代表 UTF-16BE ,就是大尾序格式,顯示的是0061
可以看到我們換成
byte[] bb= a.getBytes("UTF-16BE"); 得到的結(jié)果就是0061了
反之
byte[] bb= a.getBytes("UTF-16LE"); 得到的結(jié)果就是6100了
結(jié)論:getBytes("UTF-16")的byte長度會比我們預(yù)期的多2,就是兩個byte開頭要指定是大尾格式,還是小尾格式
- 總上,char中存儲的才是即使內(nèi)存中utf-16的unicode。 如果用string來轉(zhuǎn)成byte,就有一個重新編碼的過程。如果從string中首先取出來char,這個char的兩個字節(jié)就是真正的utf-16字符編碼
unicode的三種編碼方式
(換句話就是碼點如何轉(zhuǎn)換為utf-8或者utf-16或者utf-32)
UTF-16 是一種變長的 2 或 4 字節(jié)編碼模式。對于 BMP 內(nèi)的字符使用 2 字節(jié)編碼,其它的則使用 4 字節(jié)組成所謂的代理對來編碼。
UTF-32 采用的定長四字節(jié)則是 32 位,所以它表示所有的碼點不但毫無壓力,反而綽綽有余
UTF-8 是變長的編碼方案,可以有 1,2,3,4 四種字節(jié)組合。UTF-8 采用了高位保留方式來區(qū)別不同變長
Java中 內(nèi)碼和外碼
- 內(nèi)碼:char或String在內(nèi)存里使用的編碼方式。
- 外碼:除了內(nèi)碼都可以認(rèn)為是“外碼”。(包括class文件的編碼)
java內(nèi)碼:
unicode(utf-16)
jvm默認(rèn)外碼:
windows——gbk
Linux——utf-8
Java字符和字符串存在于以下幾個地方:
1.Java源碼文件,.java,可以是任意字符編碼,如GBK,UTF-8
2.Class文件,.class,采用的是一種改進的UTF-8編碼(Modified UTF-8)
3.JVM,內(nèi)存中使用UTF-16編碼
Java中的編碼
上邊說了一些概念,那么java中的字符編碼是怎么設(shè)計的呢?
源文件,也就是.java文件,在你的操作系統(tǒng)中新建.java文件,編寫代碼,保存,保存的時候注意保存的編碼格式,可能是asiic,gbk,utf-8等等,根據(jù)不同的編輯器,編碼方式也可能不同,如果使用idea開發(fā),setting中有個配置,就是源文件的編碼方式,這里,我改成UTF-8。總之,第一步,是寫,寫一個utf-8編碼的文件。
源文件有了,下一步編譯,也就是javac,這個時候要讀取源文件,用什么編碼方式讀呢? 默認(rèn)是操作系統(tǒng)提供
System.getProperty("file.encoding")。還可以加參數(shù),-Dfile.encoding=UTF-8。 這樣就保證讀出來的內(nèi)容是正確的。這一步,是,讀。讀取完之后,字符串需要在jvm的堆空間存儲,這時候,就用什么編碼的呢? java中字符都是unicode,存儲方式是UTF-16。這時候,一個字符可能占用2-4個字節(jié)。這一步,是寫,寫入編碼方式utf-16
假設(shè)這個源代碼實現(xiàn)的功能是把一堆漢字寫入一個新文件,或者通過網(wǎng)絡(luò)傳輸給對端。那么程序執(zhí)行,從內(nèi)存中讀取字符,這時候讀取方式肯定也是utf-16,讀取出來也是unicode,也就是說這個utf-16的處理根本無需關(guān)心,只要知道java內(nèi)存中,所有的字符都是unicode即可。注意這里不是程序源碼中指定編碼方式,而是jvm自動處理的,也就是說在jvm中,會自動處理utf-16的轉(zhuǎn)換,只要class文件中的字符編碼是正確的,就可以認(rèn)為沒有亂碼的產(chǎn)生。到這里,內(nèi)存中報錯了unicode編碼的所有字符串。
讀取完成之后,這時候字符的真正編碼又回到了最原始的源文件的編碼方式,也就是unicode 。如果網(wǎng)絡(luò)對端的操作系統(tǒng)只有一種漢字編碼,GBK。這種情況,是無法在對端顯示出正確的漢字的,因為只有支持unicode字符集的系統(tǒng),java中的漢字才能夠顯示出來,比如一個英文系統(tǒng),只支持asiic字符,那么漢字是無論如何也都無法顯示出來的。
要傳給網(wǎng)絡(luò)對端,或者寫文件,對方接受到的,文件里寫入的,也就是unicode,至于對端和文件怎么保存,那就是另一套邏輯了。
getBytes(String charsetName)new String (iso8859,Charset.forName("GBK"));這兩個方法又是怎么回事呢? 其實就是相當(dāng)于給字符串加密解密,原本是個內(nèi)存中的unicode,getByte之后,就是用charsetName重新編碼了的byte數(shù)組,如果想要還原成原樣,那么new String的時候,也要對應(yīng)的使用編碼方式進行解碼,才能拿到正確的unicode字符。System.out.println('\u61D2');會輸出漢字。因為之前所述,java中所有的字符都是unicode編碼,jvm中用utf-16存儲,所以,我們再寫代碼的時候,可以完全只使用unicode來寫代碼,這樣javac的時候,無論設(shè)置了什么參數(shù),這些unicode都不會轉(zhuǎn)換,會原樣寫入到class文件當(dāng)中。至于print的時候,只要你的操作系統(tǒng)支持unicode,那么就能顯示出正確的結(jié)果。我們可以寫下面這種代碼(可能ide會報錯,但是可以正常運行):
double π = Math.PI;
System.out.println(\u03C0);
\u0053ystem.out.println('\u61D2');
u03C0就是π
\u0053就是S
瀏覽器中的編碼
- 兩次編碼
- 很早之前,一個老工程師告訴我,在js中進行頁面跳轉(zhuǎn),或者jsp的forward跳轉(zhuǎn),如果url中涉及到中文參數(shù)的話,要進行兩次編碼。然后再后臺一次解碼,這樣,就不會亂了。
- 但是原理是啥樣的呢? 因為request.getParamter()方法,會進行一次解碼,這個是根據(jù)系統(tǒng)屬性,或者tomcat的配置,進行解碼。在這個編碼方式不確定的情況下。前臺兩次編碼,第一次編碼后中文變成了utf8.第二次編碼后因為全是assic字符,所以無論request.getParamter()是用什么解碼方式,都會得到正確的字符,因為assic碼在所有的編碼方式中,都是一致的。這樣就屏蔽了request.getParamter()的編碼差異。只要保證第一次編碼的字符集和后臺我們那一次解碼的字符集是相同的,就ok了。
- 那如果直接在瀏覽器輸入中文呢? 這個就控制不了兩次編碼了,只有一次編碼,編碼方式,就看瀏覽器的規(guī)定了。 然后如果和后臺request.getParamter()解碼的字符集不同,那么就必然會出現(xiàn)亂碼了。
問題:
- 變量名稱,首字母:英文字母或者美元符或者下劃線。非首字母:美元符或者字母或者數(shù)字或者下劃線組成。那么首字母的范圍是不是比非首字母少10個? 也是除了就0到9,首字母和非首字母范圍一致?如何驗證?
2.char多大?用幾個字節(jié)表示?能否表示中文?能否表示全部中文?
3.String nin=“恁” ,nin.getBytes().length 為什么等于3,在你同事電腦里可能等于2?是不是這個就是char的真實大?。?/p>
4.
截圖里的代碼,能運行么?會輸出什么?
5.在idea或者eclipse里,用gbk或者utf-8保存源文件,編譯出來的class文件有區(qū)別么?
jvm中是用的編碼是和源文件一致么?
6.char s = ‘再' , s+'見' 會得到什么值 ? char st =‘再'+ '見'+ '見' 呢?
7.char uy = '\377'; char uys = '\378'; 這兩種定義方式有什么起源?都能成功執(zhí)行么?
8.unicode和utf-8,utf-16到底什么關(guān)系? 什么叫code point?ucs2和ucs4是什么? ucs2能保存全世界所有的字符么?
char a=111 用整數(shù)來定義,整數(shù)的范圍是多少? char和short占用字節(jié)大小是否一樣? 能否直接轉(zhuǎn)換?
UTF-16的大尾序和小尾序儲存格式是什么樣子的?



