您的位置:首页 > 编程语言 > Java开发

浅析java中的String

2016-06-29 12:30 369 查看
转自:ImportNew微信公众号,内容有节选,推荐大家关注,每天发表一些很棒的java文章。

写这篇博文的目的,就是想验证一件事,我们知道String经常被说成不可变的字符串,当给一个String变量赋值时,其实就是在常量池分配内存空间给变量。那么常量池和常量池的字符串拼接结果是什么,仍在常量池里么?

阅读下面这段代码,试着分析一下结果:

public class StringTest{
public static void main(String[] args){
String a = "abc";
String b = "def";

String c = a + b;
String d = "abc" + "def";

String e = new String("abc");

Systen.out.println(a==e);
Systen.out.println(a.equals(e));
Systen.out.println(a=="abc");
Systen.out.println(a==e.intern());
Systen.out.println(c=="abcdef");
Systen.out.println(d=="abcdef");

}
}


在编译器中运行,结果如下:

false
true
true
true
false
true


如果你的答案是正确的,下面就不需要再看了,否则,看看我的理解,如果有不正确的,欢迎指正。

String a = “abc”;这条语句,jvm会在常量池内分配abc的内存空间给变量a(具体来说就是perm generation);我们知道,一切new的对象,都被分配在堆内存里(具体应该是堆内存的Eden空间),所以a==e判断地址是否相等时为false。而e.equals是对比值,所以肯定相等。a==”abc”两个地址是一样的,都是指向常量池的对应对象的首地址。a==e.intern()为什么也是true呢,就是当intern()这个方法发生时,它会在常量池中寻找和e这个字符串等值的字符串(匹配的方法为equals),如果没有发现则在常量池申请一个一样的字符串对象,并将对象首地址返回,如果发现了则直接返回首地址;而a是常量池中的对象,所以e在常量池中就能找到的地址就是a的首地址;

a指向常量池对象”abc”,b指向常量池对像”def”,c是a和b相加,两个都是常量池对象,d是直接等价于”abc”+”def”,两个也是常量池对象,但是和常量池对象”abcdef”相比却不一样了,这里说明开头提出的问题:两个常量池对象相加结果不一定在常量池里。

public class StringTests1{
public static void main(String[] args){
String a = "ab";
String b = "cd";

String c = a + b;
}
}


看一下编译完以后是个什么样子:

D:\Workspaces\Java\think_in_java>javap -verbose StringTest1

Classfile /D:/Workspaces/Java/think_in_java/StringTest1.class

Last modified 2016-6-29; size 465 bytes

MD5 checksum 45180a1e287d5e220a68ddd21486f4d9

Compiled from “StringTest1.java”

public class StringTest1

SourceFile: “StringTest1.java”

minor version: 0

major version: 51

flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

#1 = Methodref #9.#18 // java/lang/Object.””:()V

#2 = String #19 // ab

#3 = String #20 // cd

#4 = Class #21 // java/lang/StringBuilder

#5 = Methodref #4.#18 // java/lang/StringBuilder.””:(

)V

#6 = Methodref #4.#22 // java/lang/StringBuilder.append:(Lj

ava/lang/String;)Ljava/lang/StringBuilder;

#7 = Methodref #4.#23 // java/lang/StringBuilder.toString:(

)Ljava/lang/String;

#8 = Class #24 // StringTest1

#9 = Class #25 // java/lang/Object

#10 = Utf8

#11 = Utf8 ()V

#12 = Utf8 Code

#13 = Utf8 LineNumberTable

#14 = Utf8 main

#15 = Utf8 ([Ljava/lang/String;)V

#16 = Utf8 SourceFile

#17 = Utf8 StringTest1.java

#18 = NameAndType #10:#11 // “”:()V

#19 = Utf8 ab

#20 = Utf8 cd

#21 = Utf8 java/lang/StringBuilder

#22 = NameAndType #26:#27 // append:(Ljava/lang/String;)Ljava/l

ang/StringBuilder;

#23 = NameAndType #28:#29 // toString:()Ljava/lang/String;

#24 = Utf8 StringTest1

#25 = Utf8 java/lang/Object

#26 = Utf8 append

#27 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;

#28 = Utf8 toString

#29 = Utf8 ()Ljava/lang/String;

{

public StringTest1();

flags: ACC_PUBLIC

Code:

stack=1, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method java/lang/Object.”

“:()V

4: return

LineNumberTable:

line 1: 0

public static void main(java.lang.String[]);

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=2, locals=4, args_size=1

0: ldc #2 // String ab

2: astore_1

3: ldc #3 // String cd

5: astore_2

6: new #4 // class java/lang/StringBuilder

9: dup

10: invokespecial #5 // Method java/lang/StringBuilder.

“”:()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: return

LineNumberTable:

line 3: 0

line 4: 3

line 6: 6

line 7: 25

}

说明(这里不解释关于栈的计算指令,只说明大概意思):首先看到使用了一个指针指向一个常量池中的对象内容为“ab”,而另一个指针指向“cd”,此时通过new申请了一个StringBuilder(jdk 1.5以前是StringBuffer),然后调用这个StringBuilder的初始化方法;然后分别做了两次append操作,然后最后做一个toString()操作;可见String的+在编译后会被编译为StringBuilder来运行,我们知道这里做了一个new StringBuilder的操作,并且做了一个toString的操作,前面我们已经明确说明,凡是new出来的对象绝对不会放在常量池中;toString会发生一次内容拷贝,但是也不会在常量池中,所以在这里常量池String+常量池String放在了堆中;而下面这个后面那种情况呢,我们也用同样的方式来看看结果是什么,代码更简单了:

public class StringTests1{
public static void main(String[] args){
String d = "abc" + "def";
}
}


编译如下:

D:\Workspaces\Java\think_in_java>javap -verbose StringTest1

Classfile /D:/Workspaces/Java/think_in_java/StringTest1.class

Last modified 2016-6-29; size 286 bytes

MD5 checksum 4b68988e0fd90f4ba6df7f6e00653bd3

Compiled from “StringTest1.java”

public class StringTest1

SourceFile: “StringTest1.java”

minor version: 0

major version: 51

flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

#1 = Methodref #4.#13 // java/lang/Object.””:()V

#2 = String #14 // abcdef

#3 = Class #15 // StringTest1

#4 = Class #16 // java/lang/Object

#5 = Utf8

#6 = Utf8 ()V

#7 = Utf8 Code

#8 = Utf8 LineNumberTable

#9 = Utf8 main

#10 = Utf8 ([Ljava/lang/String;)V

#11 = Utf8 SourceFile

#12 = Utf8 StringTest1.java

#13 = NameAndType #5:#6 // “”:()V

#14 = Utf8 abcdef

#15 = Utf8 StringTest1

#16 = Utf8 java/lang/Object

{

public StringTest1();

flags: ACC_PUBLIC

Code:

stack=1, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method java/lang/Object.”

“:()V

4: return

LineNumberTable:

line 1: 0

public static void main(java.lang.String[]);

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=1, locals=2, args_size=1

0: ldc #2 // String abcdef

2: astore_1

3: return

LineNumberTable:

line 3: 0

line 4: 3

}

可以看到,当发生
“abc” + “def”
在同一行发生时,JVM在编译时就认为这个加号是没有用处的,编译的时候就直接变成
String d = "abcdef";


同理如果出现:
String a = “a” + 1
,编译时候就会变成:
String a = “a1″;


再例如:

final String a = "a";
final String b = "ab";
String c = a + b;


在编译时候,c部分会被编译为:
String c = “aab”
;但是如果a或b有任意一个不是final的,都会new一个新的对象出来;其次再补充下,如果a和b,是某个方法返回回来的,不论方法中是final类型的还是常量什么的,都不会被在编译时将数据编译到常量池,因为编译器并不会跟踪到方法体里面去看你做了什么,其次只要是变量就是可变的,即使你认为你看到的代码是不可变的,但是运行时是可以被切入的。

就是这么简单,运行时自然直接就在常量池中是一个对象了,而不需要每次访问到这里做一个加法操作,有引用的时候,JVM不确定你要拿引用去做什么,所以它并不会直接将你的字符串进行编译时的合并(其实在某些情况下JVM可以适当考虑合并,但是JVM可能是考虑到编译时优化的算法复杂性,所以这些优化可能会放在运行时的JIT来完成,但JIT优化这部分java代码是有一些前提条件的)

所以并不是常量池String+常量池String结果还在常量池,而是编译时JVM就认为他们没有必要做,直接合并了,如果是引用给出来的常量池对象,JVM在拼接过程中是通过申请StringBuilder来完成的,也就是它的结果就像普通对象一样放在堆当中的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java string