天天看點

關于String的長度限制

要了解 java 中String的運作方式,必須明确一點:String 是一個非可變類(immutable)。

什麼是非可變類呢?簡單說來,非可變類的執行個體是不能被修改的,每個執行個體中包含

的資訊都必須在該執行個體建立的時候就提供出來,并且在對象的整個生存周期内固定

不變。java 為什麼要把String 設計為非可變類呢?你可以問問 james Gosling :)。但

是非可變類确實有着自身的優勢,如狀态單一,對象簡單,便于維護。其次,該類

對象對象本質上是線程安全的,不要求同步。此外使用者可以共享非可變對象,甚至

可以共享它們的内部資訊。(詳見《Effective java》item 13)。String 類在java 中被大

量運用,甚至在class 檔案中都有其身影,是以将其設計為簡單輕便的非可變類是比

較合适的。

一、建立。

好了,知道String 是非可變類以後,我們可以進一步了解String 的構造方式了。

建立一個Stirng 對象,主要就有以下兩種方式:

java 代碼

String str1 = new String("abc");

Stirng str2 = "abc";

雖然兩個語句都是傳回一個String 對象的引用,但是jvm 對兩者的處理方式是

不一樣的。對于第一種,jvm 會馬上在heap 中建立一個String 對象,然後将該對象

的引用傳回給使用者。對于第二種,jvm 首先會在内部維護的strings pool 中通過String

的 equels 方法查找是對象池中是否存放有該String 對象,如果有,則傳回已有的

String 對象給使用者,而不會在heap 中重新建立一個新的String 對象;如果對象池中

沒有該String 對象,jvm 則在heap 中建立新的String 對象,将其引用傳回給使用者,

同時将該引用添加至strings pool 中。注意:使用第一種方法建立對象時,jvm 是不

會主動把該對象放到strings pool 裡面的,除非程式調用 String 的intern 方法。看下

面的例子:

java 代碼

String str1 = new String("abc"); //jvm 在堆上建立一個String 對象

//jvm 在strings pool 中找不到值為“abc”的字元串,是以

//在堆上建立一個String 對象,并将該對象的引用加入至strings pool 中

//此時堆上有兩個String 對象

Stirng str2 = "abc";

if(str1 == str2){

System.out.println("str1 == str2");

}else{

System.out.println("str1 != str2");

}

//列印結果是 str1 != str2,因為它們是堆上兩個不同的對象

String str3 = "abc";

//此時,jvm 發現strings pool 中已有“abc”對象了,因為“abc”equels “abc”

//是以直接傳回str2 指向的對象給str3,也就是說str2 和str3 是指向同一個對象

的引用

if(str2 == str3){

System.out.println("str2 == str3");

}else{

System.out.println("str2 != str3");

}

//列印結果為 str2 == str3

再看下面的例子:

java 代碼

String str1 = new String("abc"); //jvm 在堆上建立一個String 對象

str1 = str1.intern();

//程式顯式将str1 放到strings pool 中,intern 運作過程是這樣的:首先檢視

strings pool

//有沒“abc”對象的引用,沒有,則在堆中建立一個對象,然後将新對象的引用

加入至

//strings pool 中。執行完該語句後,str1 原來指向的String 對象已經成為垃圾對

象了,随時會

//被GC 收集。

//此時,jvm 發現strings pool 中已有“abc”對象了,因為“abc”equels “abc”

//是以直接傳回str1 指向的對象給str2,也就是說str2 和str1 引用着同一個對象,

//此時,堆上的有效對象隻有一個。

Stirng str2 = "abc";

if(str1 == str2){

System.out.println("str1 == str2");

}else{

System.out.println("str1 != str2");

}

//列印結果是 str1 == str2

為什麼jvm 可以這樣處理String 對象呢?就是因為String 的非可變性。既然所

引用的對象一旦建立就永不更改,那麼多個引用共用一個對象時互不影響。

二、串接(Concatenation)。

java 程式員應該都知道濫用String 的串接操作符是會影響程式的性能的。性能

問題從何而來呢?歸根結底就是String 類的非可變性。既然String 對象都是非可變

的,也就是對象一旦建立了就不能夠改變其内在狀态了,但是串接操作明顯是要增

長字元串的,也就是要改變String 的内部狀态,兩者出現了沖突。怎麼辦呢?要維

護String 的非可變性,隻好在串接完成後建立一個String 對象來表示新産生的字元

串了。也就是說,每一次執行串接操作都會導緻新對象的産生,如果串接操作執行

很頻繁,就會導緻大量對象的建立,性能問題也就随之而來了。

為了解決這個問題,jdk 為String 類提供了一個可變的配套類,StringBuffer。使

用StringBuffer 對象,由于該類是可變的,串接時僅僅時改變了内部資料結構,而不

會建立新的對象,是以性能上有很大的提高。針對單線程,jdk 5.0 還提供了

StringBuilder 類,在單線程環境下,由于不用考慮同步問題,使用該類使性能得到

進一步的提高。

三、String 的長度

我們可以使用串接操作符得到一個長度更長的字元串,那麼,String 對象最多

能容納多少字元呢?檢視String的源代碼我們可以得知類String中是使用域 count 來

記錄對象字元的數量,而count 的類型為 int,是以,我們可以推測最長的長度

為 2^32,也就是4G。

不過,我們在編寫源代碼的時候,如果使用 Sting str = "aaaa";的形式定義一個字

符串,那麼雙引号裡面的ASCII 字元最多隻能有 65534 個。為什麼呢?因為在class

檔案的規範中, CONSTANT_Utf8_info 表中使用一個16 位的無符号整數來記錄字

符串的長度的,最多能表示 65536 個位元組,而java class 檔案是使用一種變體UTF-8

格式來存放字元的,null 值使用兩個位元組來表示,是以隻剩下 65536- 2 = 65534

個位元組。也正是變體UTF-8 的原因,如果字元串中含有中文等非ASCII 字元,那麼

雙引号中字元的數量會更少(一個中文字元占用三個位元組)。如果超出這個數量,在

編譯的時候編譯器會報錯。