最近在研究String,對String用“+"拼接的過程很好奇,於是有了下面的描述。。。
本文先對兩個字符串拼接進行了研究,然后再對兩個字符串變量拼接進行,最后小結了一下,一起來看吧!!!
片段1:兩個字符串拼接
public class Test { public static void main(String args[]) { String str = "hello" + "world"; } }
用javap查看class文件結構
從文件結構中我們可以看出,"hello" + "world"直接被編譯成了"helloworld",並且將這個常量加載到操作數棧,再存儲到局部變量表,最后return結束。
public class Test { public static void main(String args[]) { String str1 = "hello" + "world"; String str2 = "helloworld;
System.out.print(str.equals(str));
}
}
所以,上述代碼執行equals的結果是true,String str = "hello" + "world" 和 String str = "helloworld"的效果是一樣的。
延伸:上面講述的是直接把字符串賦值給變量,那new String()是一個怎樣的過程呢?
public class Test { public static void main(String args[]) { String str = new String("helloworld"); } }
用javap查看class文件結構
從文件結構中我們可以看出,首先不管如何會先new一個String,然后復制棧頂值且將該值壓入棧頂,初始化String,再存儲到局部變量表,最后return結束。
片段2:兩個變量拼接
public class Test { public static void main(String args[]) { String str = "hello"; str = str + "world"; } }
以上這段代碼會發生什么呢?
我們先來先看一下class文件
首先將變量str存儲局部變量表里,接着new StringBuilder(),並初始化,然后調用StringBuilder.append()方法拼接字符串,再通過StringBuilder.toString()調用new String()得到一個新的字符串變量,再將這個變量存儲到局部變量表,最后return結束。
所以代碼片段2的過程其實為new StringBuilder().append(str).append("world").toString()。
既然String用加號的拼接過程用到了StringBuilder的append和toString方法,不如看看這兩個方法的過程吧。
// StringBuilder的append方法 public StringBuilder append(String str) { // 調用父類AbstractStringBuilder的append方法 super.append(str); return this; } public AbstractStringBuilder append(String str) { // 若字符串為空,返回"null" if (str == null) return appendNull(); int len = str.length(); // 獲取數組的大小,若是新的字符串的size大於char[]的length則new一個新的char[],並把原來的char賦值到新的char[]上 ensureCapacityInternal(count + len); // 把str放入char[],底層調用本地方法System.arraycopy str.getChars(0, len, value, count); count += len; return this; } // 獲取容器大小 private int newCapacity(int minCapacity) { // 若新字符串 + 已存在的字符串的大小大於char[]的大小,則新容器的大小翻倍+2 int newCapacity = (value.length << 1) + 2; if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } // 若newCapacity<Integer.MAX_VALUE - 8則char[]大小取newCapacity;若Integer.MAX_VALUE- 8<newCapacity<Integer.MAX_VALUE,取則char[]大小取Integer.MAX_VALUE;若newCapacity>Integer.MAX_VALUE,拋錯。 return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)? hugeCapacity(minCapacity): newCapacity; }
// 而StringBuilder的toString()調用的是new String() public String toString() { return new String(value, 0, count); }} public String(char value[], int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count <= 0) { if (count < 0) { throw new StringIndexOutOfBoundsException(count); } if (offset <= value.length) { this.value = "".value; return; } } if (offset > value.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } // 根據拼接后的字符串長度new一個新的char[],並把原來的char[]數組的值復制到新的char[]上,節省內存 this.value = Arrays.copyOfRange(value, offset, offset+count); } }
小結
1、兩個字符串直接拼接時,編譯器對其進行了優化,此時String str = "hello" + "world"和String str = "helloworld"是一樣的;
2、由上述的分析可以兩個字符串的拼接的底層是通過StringBuilder的append方法先將字符串放在char[]數組中,再通過toString()調用new String()新建一個合適長度的char[],再把舊的char[]復制到新的char[]上完成的,而一個小小的”+“拼接的過程經過了3次System.arraycopy,過程復雜,每次“+”都會new StringBuilder又要toString,若是在for循環中使用這種方式,可謂是嗚呼哀哉,並且每次循環一次就會new StringBuilder(),若循環的次數非常多會new非常多的StringBuilder。
最后,歡迎大家留言。。。