Java 源碼分析 — String 的設計

Tip:筆者立刻畢業了,準備開始 Java 的進階學習計劃。因而打算先從 String 類的源碼分析入手,做爲後面學習的案例。這篇文章寄託着從此進階系列產出的願望,但願能堅持下去,不忘初心,讓本身保持那份對技術的熱愛。java


由於學習分析源碼,因此借鑑了 HollisChuang 成神之路的大部份內容,並在此基礎上對源碼進行了學習,在此感謝。程序員

等風來

問題的引入

關於 String 字符串,對於Java開發者而言,這無疑是一個很是熟悉的類。也正是由於常用,其內部代碼的設計才值得被深究。所謂知其然,更得知其因此然。正則表達式

舉個例子,假如想要寫個類去繼承 String,這時 IDE 提示 String 爲final類型不容許被繼承。數據庫

此時最早想到的確定是 java 中類被 final 修飾的效果,其實由這一點也能夠引出更多思考: 好比說 String 類被設計成 final 類型是出於哪些考慮?數組

在Java中,被 final 類型修飾的類不容許被其餘類繼承,被 final 修飾的變量賦值後不容許被修改緩存


定義

查看 String 類在 JDK 7 源碼中的定義:安全

public final class String implements java.io.Serializable, Comparable<String>, CharSequence{
...
}
複製代碼

能夠看出 String 是 final 類型的,表示該類不能被其餘類繼承,同時該類實現了三個接口:java.io.Serializable Comparable<String> CharSequencebash

對於 Sting 類,官方有以下注釋說明:網絡

/*
Strings are constant; 
their values can not be changed after they are created.
Stringbuffers support mutable strings.
Because String objects are immutable they can be shared. Forexample:
*/
複製代碼

String 字符串是常量,其值在實例建立後就不能被修改,但字符串緩衝區支持可變的字符串,由於緩衝區裏面的不可變字符串對象們能夠被共享。(其實就是使對象的引用發生了改變)app


屬性

/**
The value isused for character storage.
*/
private final char value[];
複製代碼

這是一個字符數組,而且是 final 類型,用於存儲字符串內容。從 fianl 關鍵字能夠看出,String 的內容一旦被初始化後,其不能被修改的。

看到這裏也許會有人疑惑,String 初始化之後好像能夠被修改啊。好比找一個常見的例子: String str = 「hello」; str = 「hi」 其實這裏的賦值並非對 str 內容的修改,而是將str指向了新的字符串。另外能夠明確的一點:String 實際上是基於字符數組 char[] 實現的。

下面再來看 String 其餘屬性: 好比緩存字符串的 hash Code,其默認值爲 0:

/**
Cache the hashcode for the string
*/
private int hash;  //Default to 0
複製代碼

關於序列化 serialVersionUID:

/**
use serialVersionUID from JDK 1.0.2 for interoperability
*/
private static final long serialVersionUID = -6849794470754667710L;

/**
Class String is special cased with in the Serialization Stream Protocol.
*/
privates tatic final ObjectStreamField[] serialPersistentFields =  new ObjectStreamField[0]
複製代碼

由於 String 實現了 Serializable 接口,因此支持序列化和反序列化支持。Java 的序列化機制是經過在運行時判斷類的 serialVersionUID 來驗證版本一致性的。在進行反序列化時,JVM 會把傳來的字節流中的 serialVersionUID 與本地相應實體(類)的 serialVersionUID 進行比較,若是相同就認爲是一致的,能夠進行反序列化,不然就會出現序列化版本不一致的異常 (InvalidCastException)。


構造方法

空的構造器

public String(){
  this.value = "".value;
}
複製代碼

該構造方法會建立空的字符序列,注意這個構造方法的使用,由於創造沒必要要的字符串對象是不可變的。所以不建議採起下面的建立 String 對象:

String str = new String()
str = "sample";
複製代碼

這樣的結果顯而易見,會產生了沒必要要的對象。

使用字符串類型的對象來初始化

public String(String original){
  this.value = original.value;
  this.hash = original.hash;
}
複製代碼

這裏將直接將源 String 中的 value 和 hash 兩個屬性直接賦值給目標 String。由於 String 一旦定義以後是不能夠改變的,因此也就不用擔憂改變源 String 的值會影響到目標 String 的值。

使用字符數組來構造

public String(char value[]){
    this.value = Arrays.copyOf(value, value.length);
}
複製代碼
public String(char value[], int offset, int count){
  if(offset<0){
    throw new StringIndexOutOfBoundsException(offset);
  }
  if(count<=0){
    if(count<0){
     throw new String IndexOutOfBoundsException(count);
    }
    if(offset <= value.length){
      this.value = "".value;
      return;
    }
  }

 //Note:offset or count might be near-1>>>1.
  if(offset > value.length - count){
      throw new StringIndexOutOfBoundsException(offset+count);
  }
 this.value=Arrays.copyOfRange(value,offset,offset+count);
}
複製代碼

這裏值得注意的是:當咱們使用字符數組建立 String 的時候,會用到 Arrays.copyOf 方法或 Arrays.copyOfRange 方法。這兩個方法是將原有的字符數組中的內容逐一的複製到 String 中的字符數組中。會建立一個新的字符串對象,隨後修改的字符數組不影響新建立的字符串。

使用字節數組來構建 String

在 Java 中,String 實例中保存有一個 char[] 字符數組,char[] 字符數組是以 unicode 碼來存儲的,String 和 char 爲內存形式。

byte 是網絡傳輸或存儲的序列化形式,因此在不少傳輸和存儲的過程當中須要將 byte[] 數組和 String 進行相互轉化。因此 String 提供了一系列重載的構造方法來將一個字符數組轉化成 String,提到 byte[] 和 String 之間的相互轉換就不得不關注編碼問題。

String(byte[] bytes, Charset charset)
複製代碼

該構造方法是指經過 charset 來解碼指定的 byte 數組,將其解碼成 unicode 的 char[] 數組,構形成新的 String。

這裏的 bytes 字節流是使用 charset 進行編碼的,想要將他轉換成 unicode 的 char[] 數組,而又保證不出現亂碼,那就要指定其解碼方式

一樣的,使用字節數組來構造 String 也有不少種形式,按照是否指定解碼方式分的話能夠分爲兩種:

public String(byte bytes[]){
  this(bytes, 0, bytes.length);
}
複製代碼
public String(byte bytes[], int offset, int length){
    checkBounds(bytes, offset, length);
    this.value = StringCoding.decode(bytes, offset, length);
}
複製代碼

若是咱們在使用 byte[] 構造 String 的時候,使用的是下面這四種構造方法(帶有 charsetName 或者 charset 參數)的一種的話,那麼就會使用 StringCoding.decode 方法進行解碼,使用的解碼的字符集就是咱們指定的 charsetName 或者 charset。

String(byte bytes[])
String(byte bytes[], int offset, int length)
String(byte bytes[], Charset charset)
String(byte bytes[], String charsetName)
String(byte bytes[], int offset, int length, Charset charset)
String(byte bytes[], int offset, int length, String charsetName)
複製代碼

咱們在使用 byte[] 構造 String 的時候,若是沒有指明解碼使用的字符集的話,那麼 StringCoding 的 decode 方法首先調用系統的默認編碼格式,若是沒有指定編碼格式則默認使用 ISO-8859-1 編碼格式進行編碼操做。主要體現代碼以下:

static char[] decode(byte[] ba, int off, int len){
    String csn = Charset.defaultCharset().name();
    try{ //use char set name decode() variant which provide scaching.
         return decode(csn, ba, off, len);
    } catch(UnsupportedEncodingException x){
        warnUnsupportedCharset(csn);
    }

    try{
       return decode("ISO-8859-1", ba, off, len);  } 
    catch(UnsupportedEncodingException x){
       //If this code is hit during VM initiali zation, MessageUtils is the only way we will be able to get any kind of error message.
       MessageUtils.err("ISO-8859-1 char set not available: " + x.toString());
       // If we can not find ISO-8859-1 (are quired encoding) then things are seriously wrong with the installation.
       System.exit(1);
       return null;
    }
}
複製代碼

使用 StringBuffer 和 StringBuilder 構造一個 String 做爲 String 的兩個「兄弟」,StringBuffer 和 StringBuilder 也能夠被當作構造 String 的參數。

public String(StringBuffer buffer) {
   synchronized(buffer) {
   this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
   } 
} 
public String(StringBuilder builder) {
    this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
複製代碼

固然,這兩個構造方法是不多用到的,由於當咱們有了 StringBuffer 或者 StringBuilfer 對象以後能夠直接使用他們的 toString 方法來獲得 String。

關於效率問題,Java 的官方文檔有提到說使用StringBuilder 的 toString 方法會更快一些,緣由是 StringBuffer 的 toString 方法是 synchronized 的,在犧牲了效率的狀況下保證了線程安全。

StringBuilder 的 toString() 方法:

@Override
public String toString(){
  //Create a copy, don't share the array return new String(value,0,count); } 複製代碼

StringBuffer 的 toString() 方法:

@Override
public synchronized String toString(){
  if (toStringCache == null){
    toStringCache = Arrays.copyOfRange(value, 0, count);
  }
  return new String(toStringCache, true);
}
複製代碼

一個特殊的保護類型的構造方法 String 除了提供了不少公有的供程序員使用的構造方法之外,還提供了一個保護類型的構造方法(Java 7),咱們看一下他是怎麼樣的:

String(char[] value, boolean share) {
 // assert share : "unshared not supported";
 this.value = value;
}
複製代碼

從代碼中咱們能夠看出,該方法和 String(char[] value) 有兩點區別:

  • 第一個區別:該方法多了一個參數:boolean share,其實這個參數在方法體中根本沒被使用。註釋說目前不支持 false,只使用 true。那能夠判定,加入這個 share 的只是爲了區分於 String(char[] value) 方法,不加這個參數就沒辦法定義這個函數,只有參數是不一樣才能進行重載。

  • 第二個區別:具體的方法實現不一樣。咱們前面提到過 String(char[] value) 方法在建立 String 的時候會用到 Arrays 的 copyOf 方法將 value 中的內容逐一複製到 String 當中,而這個 String(char[] value, boolean share) 方法則是直接將 value 的引用賦值給 String 的 value。那麼也就是說,這個方法構造出來的 String 和參數傳過來的 char[] value 共享同一個數組。

爲何 Java 會提供這樣一個方法呢?

  • **性能好:**這個很簡單,一個是直接給數組賦值(至關於直接將 String 的 value 的指針指向char[]數組),一個是逐一拷貝,固然是直接賦值快了。

  • **節約內存:**該方法之因此設置爲 protected,是由於一旦該方法設置爲公有,在外面能夠訪問的話,若是構造方法沒有對 arr 進行拷貝,那麼其餘人就能夠在字符串外部修改該數組,因爲它們引用的是同一個數組,所以對 arr 的修改就至關於修改了字符串,那就破壞了字符串的不可變性。

  • **安全的:**對於調用他的方法來講,因爲不管是原字符串仍是新字符串,其 value 數組自己都是 String 對象的私有屬性,從外部是沒法訪問的,所以對兩個字符串來講都很安全。

Java 7 加入的新特性

在 Java 7 以前有不少 String 裏面的方法都使用上面說的那種「性能好的、節約內存的、安全」的構造函數。 好比:substring replace concat valueOf 等方法

實際上它們使用的是 public String(char[], ture) 方法來實現。

可是在 Java 7 中,substring 已經再也不使用這種「優秀」的方法了

public String substring(int beginIndex, int endIndex){
  if(beginIndex < 0){
    throw new StringIndexOutOfBoundsException(beginIndex);
  }
  if(endIndex > value.length){
    throw new StringIndexOutOfBoundsException(endIndex);
  }
  intsubLen = endIndex-beginIndex;
  if(subLen < 0){
    throw new StringIndexOutOfBoundsException(subLen);
  }
  return ((beginIndex == 0) && (endIndex == value.length)) ? this  : newString(value, beginIndex, subLen);
}
複製代碼

爲何呢? 雖然這種方法有不少優勢,可是他有一個致命的缺點,對於 sun 公司的程序員來講是一個零容忍的 bug,那就是他頗有可能形成內存泄露

看一個例子,假設一個方法從某個地方(文件、數據庫或網絡)取得了一個很長的字符串,而後對其進行解析並提取其中的一小段內容,這種狀況常常發生在網頁抓取或進行日誌分析的時候。

下面是示例代碼:

String aLongString = "...averylongstring...";
String aPart = data.substring(20, 40);
return aPart;
複製代碼

在這裏 aLongString 只是臨時的,真正有用的是 aPart,其長度只有 20 個字符,可是它的內部數組倒是從 aLongString 那裏共享的,所以雖然 aLongString 自己能夠被回收,但它的內部數組卻不能釋放。這就致使了內存泄漏。若是一個程序中這種狀況常常發生有可能會致使嚴重的後果,如內存溢出,或性能降低。

新的實現雖然損失了性能,並且浪費了一些存儲空間,但卻保證了字符串的內部數組能夠和字符串對象一塊兒被回收,從而防止發生內存泄漏,所以新的 substring 比原來的更健壯。

其餘方法

length() 返回字符串長度

public int length(){
  return value.length;
}
複製代碼

isEmpty() 返回字符串是否爲空

public boolean isEmpty(){
  return value.length == 0;
}
複製代碼

charAt(int index) 返回字符串中第(index+1)個字符(數組索引)

public char charAt(int index){
  if((index < 0) || (index >= value.length)){
    throw new StringIndexOutOfBoundsException(index);
  }
  return value[index];
}
複製代碼

char[] toCharArray() 轉化成字符數組 trim()去掉兩端空格 toUpperCase()轉化爲大寫 toLowerCase()轉化爲小寫

須要注意 String concat(String str) 拼接字符串 String replace(char oldChar, char newChar) 將字符串中的 oldChar 字符換成 newChar 字符

以上兩個方法都使用了 String(char[] value, boolean share) concat 方法和 replace 方法,他們不會致使元數組中有大量空間不被使用,由於他們一個是拼接字符串,一個是替換字符串內容,不會將字符數組的長度變得很短,因此使用了共享的 char[] 字符數組來優化。

boolean matches(String regex) 判斷字符串是否匹配給定的regex正則表達式 boolean contains(CharSequence s) 判斷字符串是否包含字符序列 s String[] split(String regex, int limit) 按照字符 regex將字符串分紅 limit 份 String[] split(String regex) 按照字符 regex 將字符串分段

getBytes

在建立 String 的時候,可使用 byte[] 數組,將一個字節數組轉換成字符串,一樣,咱們能夠將一個字符串轉換成字節數組,那麼 String 提供了不少重載的 getBytes 方法。

public byte[] getBytes(){
  return StringCoding.encode(value, 0, value.length);
}
複製代碼

可是,值得注意的是,在使用這些方法的時候必定要注意編碼問題。好比: String s = "你好,世界!"; byte[] bytes = s.getBytes(); 這段代碼在不一樣的平臺上運行獲得結果是不同的。因爲沒有指定編碼方式,因此在該方法對字符串進行編碼的時候就會使用系統的默認編碼方式。

在中文操做系統中可能會使用 GBK 或者 GB2312 進行編碼,在英文操做系統中有可能使用 iso-8859-1 進行編碼。這樣寫出來的代碼就和機器環境有很強的關聯性了,爲了不沒必要要的麻煩,要指定編碼方式。

public byte[] getBytes(String charsetName) throws UnsupportedEncodingException{
  if (charsetName == null) throw new NullPointerException();
  return StringCoding.encode(charsetName, value, 0, value.length);
}
複製代碼

比較方法

boolean equals(Object anObject); 比較對象 boolean contentEquals(String Buffersb); 與字符串比較內容 boolean contentEquals(Char Sequencecs); 與字符比較內容 boolean equalsIgnoreCase(String anotherString);忽略大小寫比較字符串對象 int compareTo(String anotherString); 比較字符串 int compareToIgnoreCase(String str); 忽略大小寫比較字符串 boolean regionMatches(int toffset, String other, int ooffset, int len)局部匹配 boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) 可忽略大小寫局部匹配

字符串有一系列方法用於比較兩個字符串的關係。 前四個返回 boolean 的方法很容易理解,前三個比較就是比較 String 和要比較的目標對象的字符數組的內容,同樣就返回 true, 不同就返回false,核心代碼以下:

int n = value.length; 
while (n-- ! = 0) {
  if (v1[i] != v2[i])
    return false;
    i++;
}
複製代碼

v1 v2 分別表明 String 的字符數組和目標對象的字符數組。 第四個和前三個惟一的區別就是他會將兩個字符數組的內容都使用 toUpperCase 方法轉換成大寫再進行比較,以此來忽略大小寫進行比較。相同則返回 true,不想同則返回 false

equals方法:

public boolean equals(Object anObject) {
     if (this == anObject) {
         return true;
     } 
    if (anObject instanceof String) {
       String anotherString = (String) anObject;
       int n = value.length;
       if (n == anotherString.value.length) {
           char v1[] = value;
           char v2[] = anotherString.value;
           int i = 0;
           while (n-- != 0) {
             if (v1[i] != v2[i])
             return false;
             i++;
           }
           return true;
       }
   } 
   return false;
}
複製代碼

該方法首先判斷 this == anObject ?,也就是說判斷要比較的對象和當前對象是否是同一個對象,若是是直接返回 true,如不是再繼續比較,而後在判斷 anObject 是否是 String 類型的,若是不是,直接返回 false,若是是再繼續比較,到了能終於比較字符數組的時候,他仍是先比較了兩個數組的長度,不同直接返回 false,同樣再逐一比較值。 雖然代碼寫的內容比較多,可是能夠很大程度上提升比較的效率。值得學習!!!

StringBuffer 須要考慮線程安全問題,加鎖以後再調用

contentEquals 有兩個重載:

  • contentEquals((CharSequence) sb) 方法 contentEquals((CharSequence) sb) 分兩種狀況,一種是 cs instanceof AbstractStringBuilder,另一種是參數是 String 類型。具體比較方式幾乎和 equals 方法相似,先作「宏觀」比較,在作「微觀」比較。

下面這個是 equalsIgnoreCase 代碼的實現:

public boolean equalsIgnoreCase(String anotherString) {
 return (this == anotherString) ? true : (anotherString != null) && (anotherString.value.length == value.length) && regionMatches(true, 0, anotherString, 0, value.length);
 }
複製代碼

看到這段代碼,眼前爲之一亮。使用一個三目運算符和 && 操做代替了多個 if 語句。

hashCode

public int hashCode(){
  int h = hash;
  if(h == 0 && value.length > 0){
    char val[] = value;
    for(int i = 0; i < value.length; i++){
      h = 31 * h + val[i];
    }
    hash = h;
  }
  return h;
}
複製代碼

hashCode 的實現其實就是使用數學公式:s[0] * 31^(n-1) + s[1] * 31^(n-2) + ... + s[n-1]

所謂「衝突」,就是在存儲數據計算 hash 地址的時候,咱們但願儘可能減小有一樣的 hash 地址。若是使用相同 hash 地址的數據過多,那麼這些數據所組成的 hash 鏈就更長,從而下降了查詢效率。

因此在選擇係數的時候要選擇儘可能長的係數而且讓乘法儘可能不要溢出的係數,由於若是計算出來的 hash 地址越大,所謂的「衝突」就越少,查找起來效率也會提升。

如今不少虛擬機裏面都有作相關優化,使用 31 的緣由多是爲了更好的分配 hash 地址,而且 31 只佔用 5 bits。

在 Java 中,整型數是 32 位的,也就是說最多有 2^32 = 4294967296 個整數,將任意一個字符串,通過 hashCode 計算以後,獲得的整數應該在這 4294967296 數之中。那麼,最多有 4294967297 個不一樣的字符串做 hashCode 以後,確定有兩個結果是同樣的。

hashCode 能夠保證相同的字符串的 hash 值確定相同,可是 hash 值相同並不必定是 value 值就相同。

substring 前面咱們介紹過,java 7 中的 substring 方法使用 String(value, beginIndex, subLen) 方法建立一個新的 String 並返回,這個方法會將原來的 char[] 中的值逐一複製到新的 String 中,兩個數組並非共享的,雖然這樣作損失一些性能,可是有效地避免了內存泄露。

replaceFirst、replaceAll、replace區別 String replaceFirst(String regex, String replacement) String replaceAll(String regex, String replacement) String replace(Char Sequencetarget, Char Sequencereplacement)

public String replace(char oldChar, char newChar){
  if(oldChar != newChar){
    int len = value.length;
    int i = -1;
    char[] val = value; /*avoid get field opcode*/
    while (++i < len){
      if (val[i] == oldChar){
        break;
      }
    }
    if( i < len ){
      char buf[] = new char[len];
      for (intj=0; j<i; j++){
        buf[j] = val[j];
      }
      while (i < len){
        char c = val[i];
        buf[i] = (c == oldChar) ? newChar : c;
        i++;
      }
      return new String(buf,true);
    }
   }
  return this;
}
複製代碼
  • replace 的參數是 char 和 CharSequence,便可以支持字符的替換, 也支持字符串的替換
  • replaceAll 和 replaceFirst 的參數是 regex,即基於規則表達式的替換

好比能夠經過 replaceAll (「\d」, 「*」)把一個字符串全部的數字字符都換成星號;

相同點是都是所有替換,即把源字符串中的某一字符或字符串所有換成指定的字符或字符串,若是隻想替換第一次出現的,可使用 replaceFirst(),這個方法也是基於規則表達式的替換。另外,若是replaceAll() 和r eplaceFirst() 所用的參數據不是基於規則表達式的,則與replace()替換字符串的效果是同樣的,即這二者也支持字符串的操做。

copyValueOf 和 valueOf String 的底層是由 char[] 實現的,早期的 String 構造器的實現呢,不會拷貝數組的,直接將參數的 char[] 數組做爲 String 的 value 屬性。字符數組將致使字符串的變化。

爲了不這個問題,提供了 copyValueOf 方法,每次都拷貝成新的字符數組來構造新的 String 對象。

如今的 String 對象,在構造器中就經過拷貝新數組實現了,因此這兩個方面在本質上已經沒區別了。

valueOf()有不少種形式的重載,包括:

public static String valueOf(boolean b) {
       return b ? "true" : "false";
 } 

public static String valueOf(char c) {
       char data[] = {c};
       return new String(data, true);
 }

 public static String valueOf(int i) {
       return Integer.toString(i);
 }

 public static String valueOf(long l) {
       return Long.toString(l);
 }

 public static String valueOf(float f) {
       return Float.toString(f);
 } 

public static String valueOf(double d) {
     return Double.toString(d);
}
複製代碼

能夠看到這些方法能夠將六種基本數據類型的變量轉換成 String 類型。

intern()方法 public native String intern(); 該方法返回一個字符串對象的內部化引用。 String 類維護一個初始爲空的字符串的對象池,當 intern 方法被調用時,若是對象池中已經包含這一個相等的字符串對象則返回對象池中的實例,不然添加字符串到對象池並返回該字符串的引用。

String 對 「+」 的重載

咱們知道,Java 是不支持重載運算符,String 的 「+」 是 java 中惟一的一個重載運算符,那麼 java 使如何實現這個加號的呢?咱們先看一段代碼:

public static void main(String[] args) {
     String string = "hello";
     String string2 = string + "world";
}
複製代碼

而後咱們將這段代碼的實際執行狀況貼出來看看:

public static void main(String args[]){
     String string = "hollo";
     String string2 = (new StringBuilder(String.valueOf(string))).append("world").toString();
}
複製代碼

看了反編譯以後的代碼咱們發現,其實 String 對 「+」 的支持其實就是使用了 StringBuilder 以及他的 append、toString 兩個方法。

String.valueOf和Integer.toString的區別 接下來咱們看如下這段代碼,咱們有三種方式將一個 int 類型的變量變成呢過String類型,那麼他們有什麼區別?

int i = 5;
String i1 = "" + i;
String i2 = String.valueOf(i);
String i3 = Integer.toString(i);
複製代碼

第三行和第四行沒有任何區別,由於 String.valueOf(i) 也是調用 Integer.toString(i) 來實現的。 第二行代碼實際上是 String i1 = (new StringBuilder()).append(i).toString();

首先建立了一個 StringBuilder 對象,而後再調用 append 方法,再調用 toString 方法。


switch 對字符串支持的實現

仍是先上代碼:

public class switchDemoString {
     public static void main(String[] args) {
         String str = "world";
         switch (str) {
         case "hello": 
              System.out.println("hello");
              break;
         case "world":
             System.out.println("world");
             break;
         default: break;
       }
    }
}
複製代碼

對編譯後的代碼進行反編譯:

public static void main(String args[]) {
       String str = "world";
       String s;
       switch((s = str).hashCode()) {
          case 99162322:
               if(s.equals("hello"))
                   System.out.println("hello");
               break;
          case 113318802:
               if(s.equals("world"))
                   System.out.println("world");
               break;
          default: break;
       }
  }
複製代碼

看到這個代碼,你知道原來字符串的 switch 是經過 equals() 和 hashCode() 方法來實現的。記住,switch 中只能使用整型,好比 byte,short,char(ackii碼是整型) 以及 int。還好 hashCode() 方法返回的是 int 而不是 long。

經過這個很容易記住 hashCode 返回的是 int 這個事實。仔細看下能夠發現,進行 switch 的實際是哈希值,而後經過使用 equals 方法比較進行安全檢查,這個檢查是必要的,由於哈希可能會發生碰撞。

所以性能是不如使用枚舉進行 switch 或者使用純整數常量,但這也不是不好。由於 Java 編譯器只增長了一個 equals 方法,若是你比較的是字符串字面量的話會很是快,好比 」abc」 ==」abc」 。若是你把 hashCode() 方法的調用也考慮進來了,那麼還會再多一次的調用開銷,由於字符串一旦建立了,它就會把哈希值緩存起來。 所以若是這個 siwtch 語句是用在一個循環裏的,好比逐項處理某個值,或者遊戲引擎循環地渲染屏幕,這裏 hashCode() 方法的調用開銷其實不會很大。

其實 swich 只支持一種數據類型,那就是整型,其餘數據類型都是轉換成整型以後在使用 switch 的。

總結

  • 一旦 String 對象在內存(堆)中被建立出來,就沒法被修改。

特別要注意的是,String 類的全部方法都沒有改變字符串自己的值,都是返回了一個新的對象。

  • 若是你須要一個可修改的字符串,應該使用 StringBuffer 或者 StringBuilder。

不然會有大量時間浪費在垃圾回收上,由於每次試圖修改都有新的String 對象被建立出來。

  • 若是你只須要建立一個字符串,你可使用雙引號的方式,若是你須要在堆中建立一個新的對象,你能夠選擇構造函數的方式。
相關文章
相關標籤/搜索