一個Java字符串中到底有多少個字符?

依照Java的文檔, Java中的字符內部是以UTF-16編碼方式表示的,最小值是 \u0000 (0),最大值是\uffff(65535), 也就是一個字符以2個字節來表示,難道Java最多隻能表示 65535個字符?

char: The char data type is a single 16-bit Unicode character. It has a minimum value of '\u0000' (or 0) and a maximum value of '\uffff' (or 65,535 inclusive).html

from The Java™ Tutorialsjava

首先,讓咱們先看個例子:api

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Main {
 
public static void main(String[] args) {
// 中文常見字
String s = "你好";
System.out.println( "1. string length =" + s.length());
System.out.println( "1. string bytes length =" + s.getBytes().length);
System.out.println( "1. string char length =" + s.toCharArray().length);
System.out.println();
 
// emojis
s = "👦👩";
System.out.println( "2. string length =" + s.length());
System.out.println( "2. string bytes length =" + s.getBytes().length);
System.out.println( "2. string char length =" + s.toCharArray().length);
System.out.println();
 
// 中文生僻字
s = "𡃁妹";
System.out.println( "3. string length =" + s.length());
System.out.println( "3. string bytes length =" + s.getBytes().length);
System.out.println( "3. string char length =" + s.toCharArray().length);
System.out.println();
}
}

運行這個程序,你以爲輸出結果是什麼?數組

輸出結果:oracle

1
2
3
4
5
6
7
8
9
10
11
1. string length =2
1. string bytes length =6
1. string char length =2
 
2. string length =4
2. string bytes length =8
2. string char length =4
 
3. string length =3
3. string bytes length =7
3. string char length =3

咱們知道, String.getBytes()若是不指定編碼格式,Java會使用操做系統的編碼格式獲得字節數組,在個人MacOS中,默認使用UTF-8做爲字符編碼(locale命令能夠查看操做系統的編碼),因此在個人機器運行,String.getBytes()會返回UTF-8編碼的字節數組。app

String.length返回Unicode code units的長度。ui

String.toCharArray返回字符數組。編碼

咱們設置的字符串都是兩個unicode字符,輸出結果:spa

  • 普通的中文字:字符串的長度是2,每一箇中文字按UTF-8編碼是三個字節,字符數組的長度看起來也沒問題
  • emojis字符: 咱們設置了兩個emojis字符,男女頭像。結果字符串的長度是4UTF-8編碼8個字節,字符數組的長度是4
  • 生僻的中文字:咱們設置了兩個中文字,其中一個是生僻的中文字。結果字符串的長度是3, UTF-8編碼7個字節,字符數組的長度是3

看起來字符串的字符數和咱們預期的有點不同,咱們的字符串只有兩個unicode字符, 但是輸出結果有時候是2,有時候是3, 有時候是4,爲何呢?
這還得從Java的歷史提及。操作系統

Java最初設計的Charactor用兩個字節來表示unicode字符,這沒有問題, 由於最初unicode中的字符還比較少, Java 1.1以前採用Unicode version 1.1.5, JDK 1.1中支持Unicode 2.0, JDK 1.1.7支持Unicode 2.1, Java SE 1.4 支持 Unicode 3.0, Java SE 5.0開始支持Unicode 4.0

直到Unicode 3.0, Java用兩個字節來表示unicode字符尚未問題,由於Unicode 3.0最多49,259個字符, 兩個字節能夠表示65,535個字符,還足夠容的下全部的uicode3.0字符。

可是Unicode 4.0(事實上自Unicode 3.1), 字符集進行很大的擴充,已經達到了96,447個字符,Unicode 11.0已經包含137,374個字符。

在Unicode中,爲每個字符對應一個編碼點(一個整數),用 U+緊跟着十六進制數表示。全部字符按照使用上的頻繁度劃分爲 17 個平面(編號爲 0-16),即基本的多語言平面和增補平面。基本的多語言平面(英文爲 Basic Multilingual Plane,簡稱 BMP)又稱平面 0,收集了使用最普遍的字符。

這樣一來,Java的Charactor的兩個字節的設計,已經不足以容納全部的Unicode 4的字符, 因此可能須要4個字節才能表示擴展字符,因此如今的Charactor表明的已經再也不是一個字符 (代碼點 code point), 而是一個代碼單元(code unit)。

  • Code Point: 代碼點,一個字符的數字表示。一個字符集通常能夠用一張或多張由多個行和多個列所構成的二維表來表示。二維表中行與列交叉的點稱之爲代碼點,每一個碼點分配一個惟一的編號數字,稱之爲碼點值或碼點編號,除開某些特殊區域(好比代理區、專用區)的非字符代碼點和保留代碼點,每一個代碼點惟一對應於一個字符。 從U+0000 到 U+10FFFF

  • Code Unit:代碼單元,是指一個已編碼的文本中具備最短的比特組合的單元。對於 UTF-8 來講,代碼單元是 8 比特長;對於 UTF-16 來講,代碼單元是 16 比特長。換一種說法就是 UTF-8 的是以一個字節爲最小單位的,UTF-16 是以兩個字節爲最小單位的。

Java的字符在內部以UTF-16編碼方式來表示,String.length返回的是Code Unit的長度,而再也不是Unicode中字符的長度。對於傳統的BMP平面的代碼點,String.length和咱們傳統理解的字符的數量是一致的,對於擴展的字符,String.length多是咱們理解的字符長度的兩倍。

有可能你會問, 對於一個UTF-16編碼的擴展字符,它以4個字節來表示,那麼前兩個字節會不會和BMP平面衝突,致使程序不知道它是擴展字符仍是BMP平面的字符?

實際上是不會的, 幸運的是, 在BMP平面中, U+D800U+DFFF之間的碼位是永久保留不映射到Unicode字符,UTF-16就利用保留下來的0xD800-0xDFFF區塊的碼位來對輔助平面的字符的碼位進行編碼。

UTF-16編碼中,輔助平面中的碼位從U+10000U+10FFFF,共計FFFFF個,須要20位來表示。第一個整數(兩個字節,稱爲前導代理)要容納上述20位的前10位,第二個整數(稱爲後尾代理)容納上述20位的後10位。前導代理的值的範圍是0xD8000xDBFF,後尾代理的0xDC00~0xDFFF。能夠看到前導代理和後尾代理的範圍都落在了BMP平面中不用來映射的碼位,因此不會產生衝突,並且前導代理和後尾代理也沒有重合。這樣咱們獲得兩個字節的,就能夠直接判斷它是不是BMP平面的字符,仍是擴展字符中的前導代理仍是後尾代碼。

國外的有些用戶用emojis字符作本身的暱稱,致使有些系統不能正確的顯示出來,這是由於這些系統粗暴的使用Charactor來表示,在顯示的時候截斷的時候有時候可能不是在正確的代碼點上進行截斷。

咱們在進行字符串截取的時候,好比String.substring有可能會踩到一些坑,尤爲常用的emojis字符。

自 Java 1.5 java.lang.String就提供了Code Point方法, 用來獲取完整的Unicode字符和Unicode字符數量:

  • public int codePointAt(int index)
  • public int codePointBefore(int index)
  • public int codePointCount(int beginIndex, int endIndex)

注意這些方法中的index使用的是code unit值。

參考文檔

    1. https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html
    2. https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Character.html
    3. http://www.oracle.com/us/technologies/java/supplementary-142654.html
    4. https://stackoverflow.com/questions/2533097/java-unicode-encoding
    5. https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html
    6. https://zh.wikipedia.org/wiki/Unicode
    7. https://codeahoy.com/2016/05/08/the-char-type-in-java-is-broken/
    8. https://zh.wikipedia.org/wiki/UTF-16
    9. https://wiki.sei.cmu.edu/confluence/display/java/STR50-J.+Use+the+appropriate+method+for+counting+characters+in+a+string
    10. http://stn.audible.com/abcs-of-unicode/#common-unicode-mistakes-in-java-apps
相關文章
相關標籤/搜索