在深刻學習字符串類以前, 咱們先搞懂JVM是怎樣處理新生字符串的. 當你知道字符串的初始化細節後, 再去寫
String s = "hello"
或String s = new String("hello")
等代碼時, 就能作到心中有數.java
public static void main(String[] args) {
String s1 = "hello";
String s2 = new String("hello");
System.out.println(s1 == s2); //false
}
複製代碼
String s1 = "hello";
幹了什麼.
String s2 = new String("hello");
的示意圖
若是上面的知識理解起來沒有問題的話, 下面看些難點的.面試
public static void main(String[] args) {
String s1 = new String("hello ") + new String("world");
s1.intern();
String s2 = "hello world";
System.out.println(s1 == s2); //true
}
複製代碼
String s1 = new String("hello ") + new String("world");
的執行過程是這樣子的:
s1.intern();
intern()
方法的詳細介紹, 翻譯過來的意思是: 當調用intern()
方法時, 首先會去常量池中查找是否有該字符串對應的引用, 若是有就直接返回該字符串; 若是沒有, 就會在常量池中註冊該字符串的引用, 而後返回該字符串.
String s2 = "hello world";
public class Main {
public static void main(String[] args) {
String s1 = "hello ";
String s2 = "world";
String s3 = s1 + s2;
String s4 = "hello world";
System.out.println(s3 == s4);
}
}
複製代碼
這道壓軸題是通過精心設計的, 它不但照應上面所講的字符串常量池知識, 也引出了後面的話題.算法
String s3 = s1 + s2;
, 咱們不知道s1 + s2
在建立完新字符串"hello world"後是否會在字符串常量池進行註冊. 說白了就是咱們不知道這行代碼是以雙引號""形式聲明字符串, 仍是用new關鍵字建立字符串.javap -c 對應.class文件的絕對路徑
, 按回車後便可看到反編譯文件的代碼段.C:\Users\liuyj>javap -c C:\Users\liuyj\IdeaProjects\Test\target\classes\forTest\Main.class
Compiled from "Main.java"
public class forTest.Main {
public forTest.Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String hello
2: astore_1
3: ldc #3 // String world
5: astore_2
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
13: aload_1
14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: aload_2
18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
25: ldc #8 // String hello world
27: astore 4
29: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
32: aload_3
33: aload 4
35: if_acmpne 42
38: iconst_1
39: goto 43
42: iconst_0
43: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
46: return
}
複製代碼
0: ldc #2 // String hello
2: astore_1
3: ldc #3 // String world
5: astore_2
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
toString()
方法得到字符串hello world
, 並存放至s3.toString()
方法源碼:@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
複製代碼
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
...
}
複製代碼
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/** * The value is used for character storage. */
char[] value;
...
}
複製代碼
並且經過StringBuilder和StringBuffer繼承自同一個父類這點, 咱們能夠推斷出它倆的方法都是差很少的. 經過查看源碼也發現確實如此, 只不過StringBuffer在方法上添加了synchronized
關鍵字, 證實它的方法絕大多數方法都是線程同步方法. 也就是說在多線程的環境下咱們應該使用StringBuffer以保證線程安全, 在單線程環境下咱們應使用StringBuilder以得到更高的效率.編程
既然如此, 咱們的比較也就落到了StringBuilder和String身上了.api
/** * 下面截取幾個String類的方法 */
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
/** * 下面截取幾個StringBuilder類的方法 */
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
@Override
public StringBuilder replace(int start, int end, String str) {
super.replace(start, end, str);
return this;
}
複製代碼
public class Main {
public static int time = 50000;
public static void main(String[] args) {
long start = System.currentTimeMillis();
String s = "";
for(int i = 0; i < time; i++){
s += "test";
}
long end = System.currentTimeMillis();
System.out.println("String類使用時間: " + (end - start) + "毫秒");
}
}
//String類使用時間: 4781毫秒
複製代碼
public class Main {
public static int time = 50000;
public static void main(String[] args) {
long start = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for(int i = 0; i < time; i++){
sb.append("test");
}
long end = System.currentTimeMillis();
System.out.println("StringBuilder類使用時間: " + (end - start) + "毫秒");
}
}
//StringBuilder類使用時間: 5毫秒
複製代碼
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String, 將""空字符串加載到棧頂
2: astore_1 //存放到s變量中
3: iconst_0 //把int型數0壓棧
4: istore_2 //存到變量i中
5: iload_2 //把i的值壓到棧頂(0)
6: getstatic #3 // Field time:I 拿到靜態變量time的值, 壓到棧頂
9: if_icmpge 38 // 比較棧頂兩個int值, for循環中的斷定, 若是i比time小就繼續執行, 不然跳轉
//從這裏開始, 就是for循環部分
12: new #4 // class java/lang/StringBuilder
15: dup
16: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
19: aload_1
20: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: ldc #7 // String test
25: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_1 //每拼接完一次, 就把新的字符串對象引用保存在第二個本地變量中
//到這裏一次for循環結束
32: iinc 2, 1 //變量i加1
35: goto 5 //繼續循環
38: return
複製代碼
從反彙編代碼中能夠看到, 當用String類拼接字符串時, 每次都會生成一個StringBuilder對象, 而後調用兩次append()方法把字符串拼接好, 最後經過StringBuilder的toString()方法new出一個新的字符串對象.數組
也就是說每次拼接都會new出兩個對象, 並進行兩次方法調用, 若是拼接的次數過多, 建立對象所帶來的時延會下降系統效率, 同時會形成巨大的內存浪費. 並且當內存不夠用時, 虛擬機會進行垃圾回收, 這也是一項至關耗時的操做, 會大大下降系統性能. 安全
下面是使用StringBuilder拼接字符串獲得的反編譯代碼.bash
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: astore_1
8: iconst_0
9: istore_2
10: iload_2
11: getstatic #4 // Field time:I
14: if_icmpge 30
//從這裏開始執行for循環內的代碼
17: aload_1
18: ldc #5 // String test
20: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: pop
//到這裏一次for循環結束
24: iinc 2, 1
27: goto 10
30: return
複製代碼
String s = "hello " + "world";
, String類的效率會更高一點.