好了,看了標題我知道你有疑問,這裡我得承認算並半個標題黨吧。
事情是這樣的:
這裡有段程序,你跑一下,結果可能跟你想的不一樣
<code>public static void main(String[] args) {
String str = "";
System.out.println(str.length());
}/<code>
你可能認為字符串長度應該是1吧,為什麼會是2呢?這裡其實就是所謂的『坑』,說到這個坑,話就有些長了,我們先看一些關於字符的概念。
以下的基礎知識我相信大多數開發的同學都知道,如果你明白直接跳過就好。
Unicode 字符集的出現就是為了統一編碼。所謂字符集就是一個由眾多不同的字符組成的集合。
Unicode 字符集對每一個字符都分配了一個唯一的 代碼點(code point) 用來標識字符本身。
所謂代碼點就是一個添加了 U+ 前綴的十六進制整數,如字母 A 的代碼點就是 U+0041。
有了Unicode 字符集後,我們要考慮的就是以什麼樣的方式對這些字符進行傳輸和存儲,這就是 Unicode 編碼的實現方式,我們稱為 Unicode 轉換格式(Unicode Transformation Format,簡稱 UTF)。我們熟悉的 UTF-8、 UTF-16 等就是不同的 Unicode編碼實現方式。
碼點如何轉換成UTF的幾種形式呢?
如上圖所示
- UTF-32採用的定長四字節則是32位
- UTF-8是變長的編碼方案,可以有1,2,3,4四種字節組合
- UTF-16是一種變長的2或4字節編碼模式
在 Unicode 字符集誕生之初,採用 UCS-2(2-byte Universal Character Set) 這種定長的編碼方式對 Unicode 字符集進行編碼,這種方式採用 16 bit 的長度來進行字符編碼,所以最多可以對 2^16 = 65536 個字符進行編碼(編碼範圍從 U+0000 ~ U+FFFF)。在當時的情況下,設計者們用了不到一半的數量就對所有字符進行了編碼,並且認為剩餘的空間足夠用於未來新增字符的編碼。
不幸的是,隨著中文、日文、韓文等表意文字不斷的加入,Unicode 字符集中的字符數量很快超過了 16 位所能編碼的最大字符數量,於是設計者們對 Unicode 字符集進行了新的設計。
新的設計將字符集中的所有字符分為 17 個 代碼平面(code plane)。其中 U+0000 ~ U+FFFF 這個代碼點範圍被劃定為 基本多語言平面(Basic MultilingualPlane,簡記為 BMP,如下圖第一個花花綠綠的那個),其餘的字符分別劃入 16 個 輔助平面(Supplementary Plane),代碼點範圍為 U+10000 ~ U+10FFFF,這些處於輔助平面的字符我們稱作
增補字符(supplementary characters)。在 Unicode 字符集中的字符被重新劃分到不同平面後,需要注意:
BMP 範圍內的字符和 UCS-2 下的字符編碼基本保持一致,但是 BMP 中的 U+D800 ~ U+DFFF 部分被留空,不分配給任何字符,作用是用於給輔助平面內的字符進行編碼。
不是每個平面內的每個位置都被分配給了指定的字符,原因是:
特殊用途,如 BMP 中的 U+D800 ~ U+DFFF 部分;
- 作為保留空間
- 沒有足夠的字符
回答程序輸出長度為2而不是1的問題
我們使用的字符其實不是普通字符,而是增補字符,我們知道 Java 中 char 的長度永遠是 16 位,如果我們在字符串中使用了增補字符,那就意味著需要 2 個 char 類型的長度才能存儲,對於 String 底層存儲字符的數組 value 來說,就需要 2 個數組元素的位置。我們再看一下String 類length方法的源碼:
<code>/**
* Returns the length of this string.
* The length is equal to the number of in the string.
*
* @return the length of the sequence of characters represented by this
* object.
*/
public int length() {
return value.length;
}/<code>
一切就明白了。java 的 String 內部用的 UTF-16 編碼,String.length() 直接返回 code unit 的個數,也就是 Java 的 2 字節 char 的個數。
當然這裡不是說絕對不要用char,只是坑多(上面只是其中一個,JDK9還有別的),建議少用而已。
閱讀更多 小盒子的技術分享 的文章