Java String與char的細節(jié)與奧秘

關(guān)于這篇博客的目的我說一下,在寫配置文件的時候忘記改編碼字符集,結(jié)果導致了全部亂碼,于是蛋疼的就去研究一下編碼集。
再說編碼集之前,我們有必要了解一些概念:

字符編碼

我們經(jīng)常用到的編碼有很多,ascll、UTF-8、UTF-16、GBK等等,可能大家認為ascll碼就是一個字符,在utf-8中一個字符就是8個字節(jié),我已開始確實是這樣認為的,然而這是錯誤的。
首先需要知道我們在 Java 中使用的是 Unicode 字符集。在其出現(xiàn)之前有已經(jīng)有了很多字符集,例如:

  • Ascll(簡單字符集):ASCII將字母、數(shù)字和其它符號編號,并用7比特二進制來表示這個整數(shù)。通常會額外使用一個擴充的比特,以便于以1個字節(jié)的方式存儲。在這種編碼模型里,一個字符集定義了這個字符集里包含什么字符,同時把每個字符如何對應(yīng)成計算機里的比特也進行了定義。例如:‘A’的十進制編碼為56.但這些字符集的局限很快就變得明顯,于是人們開發(fā)了許多方法來擴展它們。對于支持中文的要求能支持更大量的字符,并且需要一種系統(tǒng)而不是臨時的方法實現(xiàn)這些字符的編碼。
  • Unicode(現(xiàn)代編碼模型):
    統(tǒng)一碼通用字符集所構(gòu)成的現(xiàn)代字符編碼模型則沒有跟從簡單字符集的觀點。它們將字符編碼的概念分為:有哪些字符、它們的編號、這些編號如何編碼成一系列的“碼元”(有限大小的數(shù)字)以及最后這些單元如何組成八位字節(jié)流。
  • 字符--> bit
    現(xiàn)代編碼模型規(guī)定了一系列流程用來轉(zhuǎn)換字符:
    1. 抽象字符表(Abstract character repertoire):一個系統(tǒng)支持的所有抽象字符的集合
    2. 編碼字符集(CCS:Coded Character Set):給字符表里的抽象字符編上一個數(shù)字,也就是字符集合到一個整數(shù)集合的映射。例如,在一個給定的字符表中,表示大寫拉丁字母“A”的字符被賦予整數(shù)65、字符“B”是66,這是數(shù)學上的概念,這里簡化了一下,跟計算機比特沒有任何關(guān)系,這里的65就是'A'的碼位(code point)。我們平時所說的Unicode、ANSI編碼就是這一層的概念
    3. 字符編碼表(CEF:Character Encoding Form):是將編碼字符集的非負整數(shù)值(組字符對應(yīng)的整數(shù)值)轉(zhuǎn)換成有限比特長度的整型值(稱為碼元序列code units)的序列。便于以后計算機使用一定長度的二進制形式表示該整數(shù),碼元是指一個已編碼的文本中具有最短的比特組合的單元(換一種說法就是 UTF-8 的是以一個字節(jié)為最小單位的,UTF-16 是以兩個字節(jié)為最小單位的)。對于UTF-8來說,碼元是8比特長;對于UTF-16來說,碼元是16比特長;對于UTF-32來說,碼元是32比特長, 。舉個例子:就utf-8而言,對于code point 在0~65,5356 之間的字符,只需要一個碼元就可以表示,但是如果更大,則可能需要2個碼元或者三個碼元來表示,但是對于utf-16而言,可能只需要一個碼元,通俗一點說,碼元就是在指定字符編碼集下對一個字符編碼的基本元素,我們說的utf-8,utf-16便是這個層次的(https://zh.wikipedia.org/wiki/%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81#cite_note-1)。
    4. 字符編碼方案(CES:Character Encoding Scheme):對于 CEF 得到的比特值具體如何在計算機中進行存儲,傳輸。因為存在大端小端的問題,這就會跟具體的操作系統(tǒng)相關(guān)了。這種解決方案稱為字符編碼方案(CES:Character Encoding Scheme)。
      關(guān)于編碼集的歷史我不多介紹,大家可以參考wikipedia。

下面我們進入正題,首先要說明的是Java使用的是utf-16編碼,
順便也說下utf-8,

UTF-8:

UTF-8 使用一至四個字節(jié)(一個字節(jié)8bit,也就一個code unit)的序列對編碼 Unicode 代碼點進行編碼。

U+0000 至 U+007F 使用一個字節(jié)編碼

U+0080 至 U+07FF 使用兩個字節(jié)

U+0800 至 U+FFFF 使用三個字節(jié)

U+10000 至 U+10FFFF 使用四個字節(jié)


image.png
UTF-16

UTF-16

UTF-16 使用一個或兩個未分配的 16 位代碼單元的序列對 Unicode 代碼點進行編碼。
值 U+0000 至 U+FFFF 編碼為一個相同值的 16 位單元。

增補字符編碼為兩個代碼單元,第一個單元來自于高代理范圍(U+D800 至 U+DBFF),第二個單元來自于低代理范圍(U+DC00 至 U+DFFF)。

值 U+D800 至 U+DFFF 保留用于 UTF-16,這些值沒有分配給字符作為代碼點。這意味著,對于一個字符串中的每個單獨的代碼單元,軟件可以識別是否該代碼單元表示某個單單元字符,或者是否該代碼單元是某個雙單元字符的第一個或第二單元。(這里可能有點燒腦,我給大家看一段代碼)


image.png
image.png

結(jié)果分別:2 1
為啥?
這是因為對于String來說,底層使用的是char[ ],對于char來說,代表的是一個碼元為16bit的字符,對于大于16bit的字符(增補編碼),則要2個碼元表示,也就是兩個char來表示,所以Str的長度為2,但是對于碼點來說,他代表的是一個字符,所以是1,更多詳細的內(nèi)容,大家可以查閱Character的源碼文檔。

在程序中使用char

對于大多數(shù)字符來說,char是完全可以表示的,但是由于 Java 采用的是 16 位的 Unicode 字符集,即 UTF-16,所以在 Java 中 char 數(shù)據(jù)類型是定長的,其長度永遠只有 16 位,char 數(shù)據(jù)類型永遠只能表示代碼點在 U+0000 ~ U+FFFF 之間的字符,也就是在 BMP 內(nèi)的字符。如果代碼點超過了這個范圍,即使用了增補字符,那么 char 數(shù)據(jù)類型將無法支持,因為增補字符需要 32 位的長度來存儲,我們只能轉(zhuǎn)而使用 String 來存儲這個字符。

獲取字符串長度

根據(jù)上面的代碼,我們會發(fā)現(xiàn)我們的期待與實際是不一致的,實際上,其實現(xiàn)是直接返回底層 value 數(shù)組的長度。我們知道 Java 中 char 的長度永遠是 16 位,如果我們在字符串中使用了增補字符,那就意味著需要 2 個 char 類型的長度才能存儲,對于 String 底層存儲字符的數(shù)組 value 來說,就需要 2 個數(shù)組元素的位置。所以我們可以使用 codePointCount(int beginIndex, int endIndex) 來獲取長度,因為不管是處于 BMP 范圍內(nèi)的字符還是輔助平面的字符,對于code point 來說,代表的都是一個點,一個字符。
最后一點,關(guān)于使用String存儲敏感信息,不在多說,大家可以直接參考orcale文檔。
參考資料:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容