String它是一個(gè)引用數(shù)據(jù)類型,不是一個(gè)基礎(chǔ)數(shù)據(jù)類型潮模。
先思考一個(gè)問(wèn)題:String為什么是不可更改的。
查看String類的簽名如下:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {}
然后再看看String到底是怎么存儲(chǔ)字符串的:
/** The value is used for character storage. */
private final char value[];
String類的簽名,和存儲(chǔ)String的char數(shù)組都被final修飾师逸,它們確保了String對(duì)象是永遠(yuǎn)不會(huì)被修改的。
一豆混、內(nèi)存存儲(chǔ)
下面我們繼續(xù)String之旅吧篓像,先貼一段學(xué)習(xí)代碼动知。
public class LearnString {
public static void main(String args[]) {
String a = "abc";
String b = "abc";
String c = new String("abc");
System.out.println(a == b);
System.out.println(a == c);
System.out.println(a.equals(c));
}
}
運(yùn)行結(jié)果也如大家所想:
true
false
true
我們先看看String的內(nèi)存分布情況吧
public class LearnString {
String b = "abc";
public static void main(String args[]) {
String a = "abc";
}
}
編譯后,我們通過(guò)javap -v LearnString來(lái)查看內(nèi)存的分布情況员辩,可以看到在字節(jié)碼的Constant pool區(qū)有如下一行:
Constant pool:
#1 = Methodref #6.#23 // java/lang/Object."<init>":()V
#2 = String #24 // ab
#3 = Fieldref #5.#25 // com/example/learn/LearnString.d:Ljava/lang/String;
#4 = String #26 // abc
可見不管是成員變量還是局部變量盒粮,只要String一開始就被賦值了,那么它的值就會(huì)被保存在字節(jié)碼的常量池中奠滑。其實(shí)你會(huì)發(fā)現(xiàn)如果把之前的:
String d = "ab" + "d";
重新編譯后再javap后如下:
Constant pool:
#1 = Methodref #6.#23 // java/lang/Object."<init>":()V
#2 = String #24 // ab
#3 = Fieldref #5.#25 // com/example/learn/LearnString.d:Ljava/lang/String;
#4 = String #26 // abc
這時(shí)候我們可以發(fā)現(xiàn)結(jié)果是一樣的丹皱,也就是說(shuō)compiler發(fā)現(xiàn)這些"+"操作完全可以在編譯階段優(yōu)化掉,compiler就會(huì)進(jìn)行一定的優(yōu)化操作宋税。
接下來(lái)我們可以討論開篇的那個(gè)例子了摊崭。“abc”字符串按照上面所說(shuō)杰赛,在編譯的時(shí)候就被保存在字節(jié)碼的常量池中了呢簸,所以a 和 b都是拿的常量池中的“abc”值(指向了同一個(gè)堆地址),故a==b為true乏屯;c = new String("abc") 根时,它其實(shí)創(chuàng)建了兩個(gè)對(duì)象,一個(gè)new出來(lái)的另一個(gè)就是常量“abc”,c的引用指向了new出來(lái)的對(duì)象辰晕,故a!=c啸箫。
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;
}
這是String重寫Object的equals方法,由此可以發(fā)現(xiàn)equals方法是比較的是兩個(gè)String對(duì)象里頭的字符數(shù)組(char[])伞芹,比較的是堆中值而非堆地址忘苛,故a.equals(c)是相等。
二唱较、注意的問(wèn)題
先看看下面代碼
String s = "";
for(int i = 0; !"end".equals(args[i]);){
s+=args[i];
}
同樣用javap反編譯字節(jié)碼
Classfile /F:/workspaces/JavaLearn/out/production/JavaLearn/com/example/learn/LearnString.class
Last modified 2017-4-25; size 779 bytes
MD5 checksum 646b7bf02cd674af0bc5e55605ee3713
Compiled from "LearnString.java"
public class com.example.learn.LearnString
minor version: 0
major version: 50
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #10.#30 // java/lang/Object."<init>":()V
#2 = String #31 //
#3 = String #32 // end
#4 = Methodref #33.#34 // java/lang/String.equals:(Ljava/lang/Object;)Z
#5 = Class #35 // java/lang/StringBuilder
#6 = Methodref #5.#30 // java/lang/StringBuilder."<init>":()V
#7 = Methodref #5.#36 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #5.#37 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Class #38 // com/example/learn/LearnString
#10 = Class #39 // java/lang/Object
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 LocalVariableTable
#16 = Utf8 this
#17 = Utf8 Lcom/example/learn/LearnString;
#18 = Utf8 main
#19 = Utf8 ([Ljava/lang/String;)V
#20 = Utf8 i
#21 = Utf8 I
#22 = Utf8 args
#23 = Utf8 [Ljava/lang/String;
#24 = Utf8 s
#25 = Utf8 Ljava/lang/String;
#26 = Utf8 StackMapTable
#27 = Class #40 // java/lang/String
#28 = Utf8 SourceFile
#29 = Utf8 LearnString.java
#30 = NameAndType #11:#12 // "<init>":()V
#31 = Utf8
#32 = Utf8 end
#33 = Class #40 // java/lang/String
#34 = NameAndType #41:#42 // equals:(Ljava/lang/Object;)Z
#35 = Utf8 java/lang/StringBuilder
#36 = NameAndType #43:#44 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#37 = NameAndType #45:#46 // toString:()Ljava/lang/String;
#38 = Utf8 com/example/learn/LearnString
#39 = Utf8 java/lang/Object
#40 = Utf8 java/lang/String
#41 = Utf8 equals
#42 = Utf8 (Ljava/lang/Object;)Z
#43 = Utf8 append
#44 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#45 = Utf8 toString
#46 = Utf8 ()Ljava/lang/String;
{
public com.example.learn.LearnString();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/example/learn/LearnString;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: ldc #2 // String
2: astore_1
3: iconst_0
4: istore_2
5: ldc #3 // String end
7: aload_0
8: iload_2
9: aaload
10: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
13: ifne 40
16: new #5 // class java/lang/StringBuilder
19: dup
20: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
23: aload_1
24: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: aload_0
28: iload_2
29: aaload
30: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
33: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
36: astore_1
37: goto 5
40: return
LineNumberTable:
line 8: 0
line 9: 3
line 10: 16
line 12: 40
LocalVariableTable:
Start Length Slot Name Signature
5 35 2 i I
0 41 0 args [Ljava/lang/String;
3 38 1 s Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 5
locals = [ class java/lang/String, int ]
frame_type = 250 /* chop */
offset_delta = 34
}
SourceFile: "LearnString.java"
在執(zhí)行"+"時(shí)候
13: ifne 40
16: new #5 // class java/lang/StringBuilder
19: dup
20: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
23: aload_1
在這個(gè)循環(huán)里每一次對(duì)String執(zhí)行"+"操作扎唾,都會(huì)創(chuàng)建一個(gè)StringBuilder對(duì)象,可見這多么消耗性能南缓。為了避免這種事情發(fā)生只要你在執(zhí)行循環(huán)之前創(chuàng)建一個(gè)StringBuilder對(duì)象然后將之后的"+"操作換成StringBuilder.append()就可以胸遇。