StringTable
字符串比较“==”
创建字符串的几种方式
字符串字面量:如果你通过字符串字面量来创建字符串(例如
String s1 = "hello";
),并且这些字面量在编译时就能够确定,那么编译器会尝试将这些字符串放入类的常量池中。在运行时,如果两个字符串字面量具有相同的内容,并且它们都属于同一个类或同一个字符串常量池(在Java 7及更高版本中,字符串常量池是Java堆的一部分,但逻辑上仍然与类的常量池分开管理),那么它们会引用常量池中的同一个字符串对象。因此,在这种情况下,使用==
来比较这两个字符串会返回true
。通过
new
关键字创建的字符串:每次你使用new String(...)
构造函数来创建一个新字符串时,无论内容是否相同,都会在堆上创建一个新的String
对象。即使两个新创建的字符串对象包含完全相同的字符序列,它们也是不同的对象,因为它们位于内存中的不同位置。因此,在这种情况下,使用==
来比较这两个字符串会返回false
。字符串连接:当你使用
+
运算符来连接字符串时,结果取决于连接操作是在编译时还是在运行时进行的。如果连接操作涉及的是编译时常量(即字符串字面量),那么编译器会进行常量折叠,并尝试将结果放入类的常量池中。然而,如果连接操作涉及的是运行时变量,那么每次连接都会创建一个新的StringBuilder
对象(在Java 5及更高版本中),并使用其toString()
方法来生成一个新的字符串对象。
intern()
方法:String
类的intern()
方法提供了一种将字符串放入字符串常量池中的方式(如果它还不存在的话)。如果你有两个不同的字符串对象,但它们的字符序列相同,并且你调用了其中一个对象的intern()
方法,那么这两个对象在之后的比较中(使用==
)可能会返回true
,前提是另一个对象也已经通过某种方式(例如,直接作为字面量或之前调用过intern()
)被放入了字符串常量池中。
String.CASE_INSENSITIVE_ORDER
和其他比较器:值得注意的是,==
运算符总是进行引用比较。如果你需要进行内容比较而不考虑大小写或其他因素,你应该使用String
类的equals()
方法或String.CASE_INSENSITIVE_ORDER
等比较器。
inter()
总结String的intern()的使用:
jdkl.6中,将这个字符串对象尝试放入串池。
》如果串池中有,则并不会放入。返回已有的串池中的对象的地址
》如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址
Jdk1.7起,将这个字符串对象尝试放入串池。
》如果串池中有,则并不会放入。返回已有的串池中的对象的地址
》如果没有,则会把对象的引用地址复制一份,放入串池,并返回串池中的引用地址
public static void main(String[] args) {
String s=new String("1");
s.intern(); //调用此方法前,字符串常量池中已经存在了"1"
String s2="1";
System.out.println(s==s2);//jdk6:false jdk7/8:false
String s3=new String("1")+new String("1");
// 执行完上一行代码以后,字符串常量池中,是否存在"11"? 不存在
s3.intern();//在字符串常量池中生成“11”,jdk6:创建了一个新的对象“11”,也就有新的地址
// jdk7:常量池中并没有创建,而是把对象的引用地址复制一份,放入串池,并返回串池中的引用地址
String s4="11";//s4变量记录的地址:使用的是上一行代码执行时,在常量池中生成的“11”的地址
System.out.println(s3==s4);//jdk6: false jdk7/8: true
}
public void test4{
//用final修饰变成常量
final String s1="a";
final String s1="b";
String s3="ab";
String s4=s1+s2;
System.out.println(s3==s4); //true
}
字符串拼接操作不一定使用的是StringBuilder
如果拼接符号左右两边都是字符串常量或者常量引用,则仍然使用编译期优化
保证变量S指向字符串常量池中
典例
从字节码角度分析
public static void main(String[] args) {
String s1="aaaa"+"bbbb";
String s2="aaaabbbb";
String s3="aaaa";
String s4="bbbb";
String s5=s3+s2;
//拼接符号前后出现了变量,则相当于在堆空间new String()
//字符串拼接(使用+)在Java中通常会导致创建一个新的String对象。
//编译器可能会优化这个操作,使用StringBuilder来执行拼接,但这并不改变最终结果是一个新的String对象的事实。
String s6="aaaa"+s4;
String s7=s3+"bbbb";
System.out.println(s6==s7);
System.out.println(s1==s2);
System.out.println(s5==s2);
System.out.println(s6==s2);
System.out.println(s7==s2);
//intern():判断字符串常量池中是否存在“aaaabbbb”值,如果存在,则返回常量池中的“aaaabbbb”的地址
//如果不存在,则在常量池中加载一份“aaaabbbb”,并返回此对象的地址
String s8=s6.intern();
System.out.println(s2==s8);
}
true
false
false
false
false
false
true
0 ldc #2 <aaaabbbb>
2 astore_1
3 ldc #2 <aaaabbbb>
5 astore_2
6 ldc #3 <aaaa>
8 astore_3
9 ldc #4 <bbbb>
11 astore 4
13 new #5 <java/lang/StringBuilder>
16 dup
17 invokespecial #6 <java/lang/StringBuilder.<init> : ()V>
20 aload_3
21 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
24 aload_2
25 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
28 invokevirtual #8 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
31 astore 5
33 new #5 <java/lang/StringBuilder>
36 dup
37 invokespecial #6 <java/lang/StringBuilder.<init> : ()V>
40 ldc #3 <aaaa>
42 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
45 aload 4
47 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
50 invokevirtual #8 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
53 astore 6
55 new #5 <java/lang/StringBuilder>
58 dup
59 invokespecial #6 <java/lang/StringBuilder.<init> : ()V>
62 aload_3
63 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
66 ldc #4 <bbbb>
68 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
71 invokevirtual #8 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
74 astore 7
76 getstatic #9 <java/lang/System.out : Ljava/io/PrintStream;>
79 aload_1
80 aload_2
81 if_acmpne 88 (+7)
84 iconst_1
85 goto 89 (+4)
88 iconst_0
89 invokevirtual #10 <java/io/PrintStream.println : (Z)V>
92 getstatic #9 <java/lang/System.out : Ljava/io/PrintStream;>
95 aload 5
97 aload_2
98 if_acmpne 105 (+7)
101 iconst_1
102 goto 106 (+4)
105 iconst_0
106 invokevirtual #10 <java/io/PrintStream.println : (Z)V>
109 getstatic #9 <java/lang/System.out : Ljava/io/PrintStream;>
112 aload 6
114 aload_2
115 if_acmpne 122 (+7)
118 iconst_1
119 goto 123 (+4)
122 iconst_0
123 invokevirtual #10 <java/io/PrintStream.println : (Z)V>
126 getstatic #9 <java/lang/System.out : Ljava/io/PrintStream;>
129 aload 7
131 aload_2
132 if_acmpne 139 (+7)
135 iconst_1
136 goto 140 (+4)
139 iconst_0
140 invokevirtual #10 <java/io/PrintStream.println : (Z)V>
143 return
下面是对这段字节码的逐条讲解:
0 ldc #2 <aaaabbbb>
:加载常量池中的第2项(aaaabbbb
字符串)到操作数栈。
2 astore_1
:将操作数栈顶的元素(aaaabbbb
字符串)存储到局部变量表的第1个位置。
3 ldc #2 <aaaabbbb>
:再次加载常量池中的第2项(aaaabbbb
字符串)到操作数栈。
5 astore_2
:将操作数栈顶的元素(aaaabbbb
字符串)存储到局部变量表的第2个位置。
6 ldc #3 <aaaa>
:加载常量池中的第3项(aaaa
字符串)到操作数栈。
8 astore_3
:将操作数栈顶的元素(aaaa
字符串)存储到局部变量表的第3个位置。
9 ldc #4 <bbbb>
:加载常量池中的第4项(bbbb
字符串)到操作数栈。
11 astore 4
:将操作数栈顶的元素(bbbb
字符串)存储到局部变量表的第4个位置。接下来的部分涉及创建
StringBuilder
对象,并使用它来拼接字符串:
13 new #5 <java/lang/StringBuilder>
:在堆上分配一个新的StringBuilder
对象引用。
16 dup
:复制操作数栈顶的元素(新分配的StringBuilder
对象引用)。
17 invokespecial #6 <java/lang/StringBuilder.<init> : ()V>
:调用StringBuilder
的构造方法初始化对象。
20 aload_3
:加载局部变量表中第3个位置的元素(aaaa
字符串)到操作数栈。
21 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
:调用StringBuilder
的append
方法,将aaaa
字符串追加到StringBuilder
对象中。
24 aload_2
:加载局部变量表中第2个位置的元素(aaaabbbb
字符串)到操作数栈。
25 invokevirtual #7
:再次调用append
方法,将aaaabbbb
字符串追加到StringBuilder
对象中。
28 invokevirtual #8 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
:调用toString
方法,将StringBuilder
对象转换为字符串。
31 astore 5
:将操作数栈顶的字符串存储到局部变量表的第5个位置。接下来的部分重复上述步骤,但使用不同的字符串和局部变量槽:
创建一个新的
StringBuilder
对象,并使用aaaa
和局部变量表第4个位置的字符串(bbbb
)构建一个新的字符串,存储在局部变量表的第6个位置。再次创建一个新的
StringBuilder
对象,并使用aaaa
和常量池中的bbbb
字符串构建另一个字符串,存储在局部变量表的第7个位置。最后的部分涉及条件判断和打印输出:
使用
if_acmpne
指令比较局部变量表第1个和第2个位置的字符串是否不相等,如果不相等,跳转到指定的指令处(用于控制打印输出布尔值false
)。类似的条件判断用于比较局部变量表第5个和第2个位置的字符串、第6个和第2个位置的字符串、以及第7个和第2个位置的字符串,每次比较后都打印一个布尔值。
143 return
:方法返回。