探索 Java 中 String 的本質(zhì)班眯,從 char 說(shuō)起

String 類(lèi)可以認(rèn)為是 Java 語(yǔ)言中最為常用的類(lèi)了,對(duì)于 String 的理解更是 Java 面試題的扯疲客淳衙。
但作為一個(gè) Java 程序員,對(duì)于 String 是否足夠了解了呢饺著?
本篇文章將對(duì) String的存儲(chǔ)箫攀,使用做一個(gè)詳細(xì)的探討。


先來(lái)簡(jiǎn)單介紹下 String幼衰,String是 JDK 提供的位于 java.lang 中的基礎(chǔ)類(lèi)靴跛,但區(qū)別于 byte,short渡嚣,int梢睛,long,char严拒,boolean扬绪,float竖独,double這些基本類(lèi)型裤唠,String不是基本數(shù)據(jù)類(lèi)型,而是一個(gè)類(lèi)莹痢。
因?yàn)槭穷?lèi)种蘸,實(shí)例化的String 對(duì)象的空值為 null,但String是如此常用竞膳,于是 JDK 對(duì)其有特殊的優(yōu)化航瞭。


String 的存在形式

上文提到,String是 JDK 提供的類(lèi)坦辟,要學(xué)習(xí) JDK刊侯,最好的方法就是閱讀其源碼。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    ...

分析源碼可以知道锉走,Java 中String 是以 char 數(shù)組的形式存在的滨彻。

討論下 Java 中的 char 類(lèi)型

char 基本數(shù)據(jù)類(lèi)型是 Java 中用于存儲(chǔ)字符的藕届。String 就是以 char數(shù)組形式存儲(chǔ)的,要理解 String 就必須先了解 char亭饵。
但在討論 char 之前休偶,還需要介紹另外兩點(diǎn)知識(shí)。

編碼 unicode vs UTF

unicode辜羊,稱(chēng)為統(tǒng)一字符編碼踏兜,是國(guó)際上對(duì)千奇百怪字符的統(tǒng)一的編號(hào)。unicode 最初的 256 個(gè)字符八秃,是繼承于 ASCII 編碼碱妆。例如英文字母 a,在 unicode 中編號(hào)為 97昔驱,中文 字山橄,在 unicode 中的編號(hào)就是 25105。簡(jiǎn)單的說(shuō)舍悯,unicode 就是統(tǒng)一的字符編號(hào)航棱。但是,這么多字符如何在代碼中表示呢萌衬?這便是 UTF饮醇。


UTF,unicode 轉(zhuǎn)換格式(Unicode Transformation Format)秕豫,UTF 有多種編碼方式朴艰,比較常用的就是 UTF-8UTF-16,這兩者各有優(yōu)劣混移,UTF-8 信息密度更高祠墅,傳輸、存儲(chǔ)效率更高歌径,UTF-16字符對(duì)齊毁嗦,易于程序處理,利于優(yōu)化計(jì)算效率回铛。使用應(yīng)視情形而定狗准。

  • UTF-8 是通過(guò)變長(zhǎng)來(lái)表示 unicode 字符的,以 byte 為單位茵肃,長(zhǎng)度范圍 1~6腔长。例如 a 編號(hào)是 97,就用一個(gè) byte验残,也就是 8 bit 來(lái)編碼捞附,而 的編號(hào)是 25105,一個(gè) byte 無(wú)法編碼,于是就用 2個(gè) byte 來(lái)編碼鸟召。
  • UTF-16 則是固定長(zhǎng)度編碼想鹰。統(tǒng)一用 2 byte,也就是 16bit 進(jìn)行編碼药版,但 unicode 當(dāng)前字符集已經(jīng)超出 16bit 所能編碼范圍了(16bit辑舷,可對(duì) 2^16 = 65536 個(gè)字符進(jìn)行編碼),因此也會(huì)用 4 byte 來(lái)表示槽片。


內(nèi)碼與外碼

內(nèi)碼 internal encoding何缓,外碼 external encoding

  • 內(nèi)碼是語(yǔ)言運(yùn)行時(shí),char 在內(nèi)存中的編碼方式还栓。
  • 外碼是除了內(nèi)碼以外的編碼碌廓,例如源碼編譯生成的目標(biāo)文件(可執(zhí)行文件、.class 文件)中的編碼均為外碼剩盒。


那么 Java 中的 char 呢谷婆?

char 是 Java 的基本類(lèi)型之一,用來(lái)表示字符辽聊。
JVM 采用的內(nèi)碼纪挎,是 UTF-16,也就是說(shuō) Java 中的 char 的長(zhǎng)度為 2 byte跟匆,即 16 bit异袄。
但上文提到,僅 16 bit 已無(wú)法表示所有的 unicode玛臂,因此為了向下兼容烤蜕,Java 的 char 保留為 16bit,若有無(wú)法用 16 bit 表示的字符迹冤,則采用 2 char讽营,即 4 byte,32 bit 來(lái)表示泡徙。

Java 的 class 文件采用 UTF-8 存儲(chǔ)字符橱鹏。char 在 class 中以 UTF-8 方式編碼,區(qū)別于內(nèi)碼中的 char


Character锋勺,關(guān)于 char 的更多

Java 采用 UTF-16 為字符編碼蚀瘸。但 unicode 字符集已經(jīng)超出 16bit 所能表述的范圍狡蝶,因此有些字符會(huì)采用 2char庶橱,即 32 bit 進(jìn)行編碼。
為了方便處理贪惹,Java 提供了 Character 類(lèi)苏章。Character 對(duì) char 進(jìn)行了封裝,并提供了一些方法,主要是char 類(lèi)型的判斷(是數(shù)字還是中文)枫绅、大小寫(xiě)裝換泉孩、比較等等。具體方法并淋,可以參考 JDK 源碼java.lang.Character寓搬。


提到 Character,主要是強(qiáng)調(diào)以下幾點(diǎn):

  1. code point vs code unit
    碼位 code point:指字符在 unicode 字符集中的編號(hào)县耽,用 int 表示句喷,int 為 32bit,現(xiàn)階段可表示 unicode 字符集兔毙。范圍為 U+0000 ~ U+10FFFF唾琼。
    code unit:對(duì)應(yīng)一個(gè) char,可由 1個(gè)或 2個(gè) code unit 組成 code point澎剥。這兩個(gè)概念主要涉及 UTF-16 實(shí)現(xiàn)锡溯。

  2. 基本多語(yǔ)言平面 Basic Multilingual Plane (BMP) vs 輔助平面 Supplementary Character
    這兩個(gè)概念,是針對(duì) unicode 字符集而言哑姚。當(dāng)前 Java 支持的 unicode 字符集范圍為 U+0000 ~ U+10FFFF祭饭,若超出此范圍,則無(wú)法處理叙量。
    Basic Multilingual Plane (BMP):用于表示 U+0000 ~ U+FFFF 范圍的字符甜癞。
    Supplementary Character:unicode 超出 U+FFFF 范圍后,需要用 2個(gè) char 表示宛乃,超出部分稱(chēng)為 Supplementary Character悠咱,由于 code point 范圍最大為 U+10FFFF,所以 Supplementary Character 最多為 5bit征炼,高位的 11bit 必須均為 0析既,否則表示字符超出 Java 當(dāng)前字符集范圍。處理單個(gè) char時(shí)谆奥,不需要使用 Supplementary Character眼坏,當(dāng)以 int 表示字符時(shí),才需要使用酸些。
    具體可參考維基百科 UTF-16 介紹宰译。


String 是 char[]

以上分析源碼,知道了 String 是以 final char[] 的形式存儲(chǔ)的魄懂,并且知道了由于 Java 采用 UTF-16 編碼 unicode沿侈,因此有些字符由 2 char 表示。

int len1 = "1".length();  // = 1
int len2 = "我".length();  // = 1
int len3 = "??".length(); // = 2

// 用以下方法獲得真正的 unicode 字符個(gè)數(shù)
String emoji = "??";
int len3 = emoji.codePointCount(0, emoji.length());

String 類(lèi)中還提供了一些常用的字符處理方法市栗,將在下面的實(shí)踐章節(jié)進(jìn)行介紹缀拭,讓我們下來(lái)看看 String 是如何在 JVM 中存儲(chǔ)的咳短。

Java 中 String 的存儲(chǔ)

  1. String 底層是 final char[],是常量蛛淋。在 JVM 中咙好,位于字符串常量池。所謂常量褐荷,就是一旦創(chuàng)建勾效,就不無(wú)更改。
  2. 只要 String 的值發(fā)生變更叛甫,Java 的處理方式是新建一個(gè) String對(duì)象葵第。
  3. 由于 String 是類(lèi),其實(shí)例為對(duì)象合溺。Java 在處理對(duì)象傳遞是卒密,均是引用拷貝
  4. 對(duì) String 的只讀棠赛,任何引用均不會(huì)修改其值哮奇。


JDK1.7 中 JVM 把String常量池從方法區(qū)中移除了;JDK1.8 中 JVM 把String常量池移入了堆中睛约,同時(shí)取消了“永久代”鼎俘,改用元空間代替(Metaspace)

運(yùn)行時(shí)常量池中的內(nèi)容,主要源于 class 靜態(tài)常量池辩涝,也就是編譯階段確定的常量池贸伐。但也可以通過(guò) String.intern() 方法,手動(dòng)將字符串常量放入運(yùn)行時(shí)常量池中怔揩,否則 JVM 不會(huì)主動(dòng)添加常量至常量池捉邢。

為何選擇常量池存放 String

常量池是為了避免頻繁的創(chuàng)建和銷(xiāo)毀對(duì)象而影響系統(tǒng)性能,其實(shí)現(xiàn)了對(duì)象的共享商膊。
例如字符串常量池伏伐,在編譯階段就把所有的字符串文字放到一個(gè)常量池中。

  1. 節(jié)省內(nèi)存空間:常量池中所有相同的字符串常量被合并晕拆,只占用一個(gè)空間藐翎。
  2. 節(jié)省運(yùn)行時(shí)間:比較字符串時(shí),==equals()快实幕。對(duì)于兩個(gè)引用變量吝镣,只用==判斷引用是否相等,也就可以判斷實(shí)際值是否相等昆庇。

String 何時(shí)為常量末贾,入常量池

何時(shí)視為常量,何時(shí)入常量池凰锡?先了解什么是常量表達(dá)式和 ==equals() 的區(qū)別吧未舟。

常量表達(dá)式

要解決這個(gè)問(wèn)題圈暗,要先理解常量表達(dá)式掂为。
常量表達(dá)式:指代表基本數(shù)據(jù)類(lèi)型或者 String 數(shù)據(jù)類(lèi)型的表達(dá)式裕膀,能在編譯期間能計(jì)算出來(lái)的值,因此表達(dá)式中的均需為常量勇哗,不可為變量昼扛。
對(duì)于常量表達(dá)式,Java 編譯時(shí)會(huì)進(jìn)行優(yōu)化欲诺,直接賦予計(jì)算后的常量值抄谐。

==equals()

  • == : 判斷兩個(gè)對(duì)象是否為同一對(duì)象,即判斷引用的是否為同一個(gè)對(duì)象扰法。
  • equals():判斷兩個(gè)對(duì)象的值是否相同蛹含。類(lèi)中默認(rèn)的 equals()== 判斷,但可被自定義覆蓋塞颁。

舉例

了解了常量表達(dá)式浦箱,來(lái)看看下面的實(shí)例。

private final static String staticA = "AAA";   // 常量
private final static String staticB = "111";   // 常量
private final static String staticC;
private final static String staticD;
private final static String staticE;
static {
    staticC = "AAA";
    staticD = "111";
    staticE = "AAA111";
}

public static void main(String[] args) {
    String str0 = "AAA111";
    String str1 = "AAA" + "111";
    String str2 = staticA + staticB;
    String str3 = staticC + staticD;
    String str4 = "AAA" + 111;
    String str5 = staticA + 111;
    String str6 = staticC + 111;
    String str7;
    str7 = staticC + staticD;
    String str8;
    str8 = str7 + "";
    
    String str9 = str8.intern();
    System.out.println(str0 == str8);   // true
}

看如下代碼祠锣,其中 str0~str8 的值均為 AAA111酷窥。
但當(dāng)彼此進(jìn)行 == 操作時(shí),卻不均為 true伴网,說(shuō)明底層并未指向相同的對(duì)象蓬推。


staticE, str0, str1, str2, str4, str5,str9 彼此進(jìn)行 == 判斷時(shí),為 true澡腾。
staticA == staticC沸伏,staticB == staticDtrue
str3, str6, str7, str8 彼此均為 false动分。

image

此圖為 Java8 示意馋评,Java8 之前的運(yùn)行時(shí)常量池是在方法區(qū)。


對(duì)以上代碼分析:
staticA ~ staticE 五個(gè)變量刺啦,均為 final常量留特。但 staticC~staticEstaticA,staticB 略有區(qū)別玛瘸,staticC~staticD 雖然是常量蜕青,但在編譯期未被賦值,是到運(yùn)行時(shí)才被賦值糊渊,因此性質(zhì)類(lèi)似于一個(gè)變量右核,不可視為編譯時(shí)常量。staticE 也是變量渺绒,但賦值直接為 AAA111贺喝。

str0~str8 部分菱鸥,均為棧內(nèi)定義的變量。

  1. str0 在編譯時(shí)躏鱼,直接賦值氮采,執(zhí)行的是常量表達(dá)式。AAA111 入常量池染苛,str0 為其引用鹊漠。
  2. str1 在編譯時(shí),是由兩個(gè)常量 AAA111 連接所得茶行,值也可以確定躯概。由于 str0 時(shí),已經(jīng)將 AAA111 放入常量池畔师,因此 str1 復(fù)用娶靡,引用同一常量池對(duì)象。
  3. str2staticAstaticB 連接看锉,由于 staticA姿锭,staticB 值是常量,執(zhí)行的是常量表達(dá)式度陆,引用常量池艾凯。
  4. str3staticCstaticD 連接,但 staticCstaticD 未被直接賦值懂傀,編譯期無(wú)法決定值趾诗。
  5. str4str5 均能在編譯期決定值,因此也引用常量池
  6. str6~str8 均無(wú)法在編譯期決定值蹬蚁,因此不引用常量池恃泪。
  7. str9 使用了 String.inertn()若字符串已在常量池存在犀斋,則引用已有常量池對(duì)象贝乎,若不存在,則會(huì)手動(dòng)將字符串放入字符串常量池叽粹,并引用览效。



討論完常量池的情況,再來(lái)看看堆的情況虫几。

String sA = "ABCD";
String sB = new String("ABCD");
String sC = new String("ABCD").intern();
System.out.println(sA == sB);   // false
System.out.println(sA == sC);   // true
System.out.println(sB == sC);   //false

如上代碼锤灿,當(dāng) new 一個(gè)對(duì)象時(shí),Java 會(huì)將其放置于堆中辆脸。因此但校,顯然不會(huì)與常量池中的引用相等,sA == sB 為 false啡氢。
但如上文所述状囱,如果主動(dòng)調(diào)用 String.intern() 方法术裸,則會(huì)將字符串放入常量池,此處 ABCD 字符串已存在亭枷,因此sC 直接引用常量池中的字符串對(duì)象袭艺。


仔細(xì)分析可知,在 new String("ABCD") 時(shí)奶栖,可能創(chuàng)建一個(gè)或兩個(gè)對(duì)象匹表。若 new 的字符串已經(jīng)存在门坷,則僅會(huì)在堆上創(chuàng)建一個(gè)對(duì)象宣鄙,但若字符串不存在,則會(huì)先在常量池中創(chuàng)建默蚌,然后再堆中創(chuàng)建對(duì)該字符串的引用冻晤。

String 實(shí)踐

這部分,主要是總結(jié) 《Java 編程思想》13章字符串章節(jié)绸吸。

JDK 中 + 的重載與 StringBuilder 優(yōu)化

由于 String 對(duì)象的不可變鼻弧。每次對(duì)字符串的變更,均會(huì)創(chuàng)建一個(gè)新的對(duì)象锦茁,那么出現(xiàn)下面情況時(shí)攘轩,會(huì)產(chǎn)生大量的中間變量,使得代碼效率降低码俩。

String hello = "h" + "e" + "l" + "l" + "o";

若不進(jìn)行優(yōu)化度帮,上面代碼會(huì)在字符串常量池中創(chuàng)建 h, e, l, o, he, hel, hell, hello,這么多中間對(duì)象稿存。
Java 對(duì)此進(jìn)行了優(yōu)化笨篷。


以下代碼為例

public static void main(String[] args) {
    String str1 = "abc";
    String str2 = str1 + "h" + "e" + "l" + "l" + "o";
}

利用 JDK 提供的 javap -c XXXX 反編譯工具,可以看到底層實(shí)現(xiàn)瓣履。

0: ldc           #2                  // String abc
2: astore_1
3: new           #3                  // class java/lang/StringBuilder
6: dup
7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
10: aload_1
11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc           #6                  // String h
16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc           #7                  // String e
21: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: ldc           #8                  // String l
26: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
29: ldc           #8                  // String l
31: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: ldc           #9                  // String o
36: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
39: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
42: astore_2

可以發(fā)現(xiàn)率翅,編譯器自動(dòng)引入了 StringBuilder 類(lèi),在每次重載 + 時(shí)袖迎,底層均調(diào)用一次 StringBuilder.append() 方法冕臭。這減少了中間對(duì)象,提高了效率燕锥。

雖然編譯器會(huì)幫助我們優(yōu)化辜贵,但用 + 效率還是比較低。這是因?yàn)槊看螆?zhí)行字符串 +脯宿,都會(huì)創(chuàng)建 StringBuilder對(duì)象念颈。

String str1 = "";
for (int i = 0; i < 100; i++) {
    str1 += i;
}

對(duì)應(yīng)反編譯字節(jié)碼為,從 6~18 行為循環(huán)连霉,第 10行榴芳,會(huì)創(chuàng)建 StringBuilder 對(duì)象嗡靡。在循環(huán)中,創(chuàng)建對(duì)象窟感,調(diào)用了兩次 append() 方法和一次 toString() 方法讨彼,效率不高。

0: ldc           #2                  // String
2: astore_1
3: iconst_0
4: istore_2
5: iload_2
6: bipush        100
8: if_icmpge     36
11: new           #3                  // class java/lang/StringBuilder
14: dup
15: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
18: aload_1
19: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: iload_2
23: invokevirtual #6                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
26: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29: astore_1
30: iinc          2, 1
33: goto          5

因此還是推薦主動(dòng)創(chuàng)建 StringBuilder 對(duì)象柿祈」螅可以?xún)?yōu)化為

String str1 = "";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
    sb.append(i);
}
str1 = sb.toString();

反編譯結(jié)果如下,可以看到在循環(huán)外創(chuàng)建了一次 StringBuilder,并且循環(huán)內(nèi)也只調(diào)用了一次 append() 方法站蝠,最終調(diào)用了一次 toString()奥务。

0: ldc           #2                  // String
2: astore_1
3: new           #3                  // class java/lang/StringBuilder
6: dup
7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
10: astore_2
11: iconst_0
12: istore_3
13: iload_3
14: bipush        100
16: if_icmpge     31
19: aload_2
20: iload_3
21: invokevirtual #5                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
24: pop
25: iinc          3, 1
28: goto          13
31: aload_2
32: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
35: astore_1


避免 toString() 無(wú)意識(shí)的遞歸

Java 的所有類(lèi)均繼承于 Object,因此所有類(lèi)均可重寫(xiě) toString() 方法重荠,toString 方法常被用于打印對(duì)象的基本信息。
但如果在 toString 方法中虚茶,用到了 this戈鲁,便會(huì)出現(xiàn)無(wú)限遞歸,報(bào) StackOverflowError 異常嘹叫。例如以下代碼:

public class InfRec {
    @Override
    public String toString() {
        return "InfRec" + this;
    }

    public static void main(String[] args) {
        System.out.println(new InfRec());
    }
}

應(yīng)該將 this 改為 super.toString()婆殿;


StringBuilder vs StringBuffer

  • StringBuilder:非線性安全,效率更高罩扇,于 Java 5中加入
  • StringBuffer:線性安全婆芦,使用了 synchronized 關(guān)鍵字。效率低暮蹂,不推薦使用寞缝,即使是多線程環(huán)境,也有更好的方案仰泻。


更多 String 使用荆陆,參考 JDK 源碼


總結(jié)

  1. String 不是基礎(chǔ)數(shù)據(jù)類(lèi)型,是一個(gè)類(lèi)集侯,默認(rèn)值是 null 而非 ""被啼。
  2. String 是由 char[] 構(gòu)成,Java 內(nèi)碼采用 UTF-16 對(duì) unicode 編碼棠枉。因此存在一個(gè)字符長(zhǎng)度為 2的情況浓体,如 ?? 對(duì)應(yīng)的 \uD83D\uDE02
  3. String 為常量辈讶,一旦定義不可變更命浴。若修改,會(huì)創(chuàng)建新的對(duì)象。
  4. String 傳遞時(shí)為引用拷貝生闲。
  5. 通過(guò)定義常量或者常量表達(dá)式媳溺,可以于編譯期確定 String的值的,會(huì)將該字符串放入 class 靜態(tài)常量池碍讯,當(dāng)類(lèi)加載時(shí)悬蔽,載入至運(yùn)行時(shí)常量池。
  6. 可通過(guò) String.intern() 方法捉兴,主動(dòng)將字符串放置入常量池蝎困,若常量池已存在該字符串,會(huì)直接引用倍啥。若不主動(dòng)調(diào)用 intern() 方法禾乘,JVM 不會(huì)主動(dòng)將字符串放入常量池。
  7. new String("ABCD") 過(guò)程逗栽,會(huì)創(chuàng)建一個(gè)或兩個(gè)對(duì)象盖袭,或有一個(gè)位于常量池失暂,另一個(gè)位于堆中彼宠。
  8. 當(dāng)代碼涉及較多字符串 + 操作時(shí),使用 StringBuilder 能提高效率
  9. 不要在 toString 方法中使用 this弟塞,避免無(wú)限遞歸凭峡,應(yīng)該用 super.toString()
  10. StringBuilder 非線性安全,StringBuffer 使用了synchronized 關(guān)鍵字决记,效率低摧冀,不推薦使用。


參考資料

[1] 深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐(第2版)系宫,作者周志明
[2] 《Java 編程思想》第4版索昂,作者 Bruce Eckel
[3] class文件常量池和運(yùn)行時(shí)常量池比對(duì), http://www.ifcoding.com/archives/284.html
[4] 什么是字符串常量池扩借?椒惨, http://www.importnew.com/10756.html
[5] Java篇-String詳解, TianTianBaby223潮罪,http://www.reibang.com/p/d832752caf0c
[6] Java常用類(lèi)(二)String類(lèi)詳解康谆, https://www.cnblogs.com/zhangyinhua/p/7689974.html
[7] String類(lèi)詳解, https://juejin.im/post/59f6eb076fb9a045154329cc
[8] Top 10 questions of Java Strings嫉到,http://www.programcreek.com/2013/09/top-10-faqs-of-java-strings/
[9] Java中String詳解沃暗,作者 Lolita, https://zhuanlan.zhihu.com/p/29629508

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末何恶,一起剝皮案震驚了整個(gè)濱河市孽锥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌细层,老刑警劉巖惜辑,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隔崎,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡韵丑,警方通過(guò)查閱死者的電腦和手機(jī)爵卒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)撵彻,“玉大人钓株,你說(shuō)我怎么就攤上這事∧敖” “怎么了轴合?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)碗短。 經(jīng)常有香客問(wèn)我受葛,道長(zhǎng),這世上最難降的妖魔是什么偎谁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任总滩,我火速辦了婚禮,結(jié)果婚禮上巡雨,老公的妹妹穿的比我還像新娘闰渔。我一直安慰自己,他們只是感情好铐望,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布冈涧。 她就那樣靜靜地躺著,像睡著了一般正蛙。 火紅的嫁衣襯著肌膚如雪督弓。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天乒验,我揣著相機(jī)與錄音愚隧,去河邊找鬼。 笑死徊件,一個(gè)胖子當(dāng)著我的面吹牛奸攻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播虱痕,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼睹耐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了部翘?” 一聲冷哼從身側(cè)響起硝训,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后窖梁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體赘风,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年纵刘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了邀窃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡假哎,死狀恐怖瞬捕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情舵抹,我是刑警寧澤肪虎,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站惧蛹,受9級(jí)特大地震影響扇救,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜香嗓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一迅腔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧陶缺,春花似錦钾挟、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)徽千。三九已至苫费,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間双抽,已是汗流浹背百框。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留牍汹,地道東北人铐维。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像慎菲,于是被迫代替她去往敵國(guó)和親嫁蛇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容