字符串常量池在1.6与1.8以及String、StringBuffer、StringBuilder

欢迎查看Eetal的第十八篇博客–字符串常量池在1.6与1.8以及String、StringBuffer、StringBuilder

字符串加入常量池时机

直接使用双引号声明出来的String对象会直接存储在常量池中,相同字符串使用同一个对象
而使用构造函数创建的会新创建一个字符串对象,但是该字符串对象的字符数组value会引用常量池中的字符串对象的字符数组
使用String的concat方法拼接后的字符串使用的是share模式的构造函数进行构造,默认不会引用常量池(此处的share模式有点threadLocal的那种意思,所以是创建副本不去引用常量池)
对于不进入常量池而常量池中没有的字符串,可以显示调用String的intern方法将其加入常量池
引用自博客https://blog.csdn.net/chen1280436393/article/details/51768609
也就是说,代码里双引号直接包含的字符串会被放进常量池
比如

1
2
3
4
5
6
7
8
9
String str1 = "我爱java";//我爱java进入常量池
String str2 = "歪歪"+"梯";//因为编译器会优化,所以等价于"歪歪梯",歪歪梯会放入常量池
String str3 = new String("构造1");//构造1会放入常量池
String str4 = new String("构造2")+"测试";//构造2会放入常量池。测试会放入常量池
String str5 = new String("构造3")+new String("构造4");//构造3会放入常量池。构造4会放入常量池
/*
*最终常量池包括 我爱java 歪歪梯 构造1 构造2 构造3 构造4 测试 7个字符串
*注意非字面常量字符串拼接的结果字符串默认并不会加入常量池(因此也不会默认去引用常量池,这个下面会验证)
*/

如果是直接使用常量字符串赋值的,会直接引用常量池该字符串对象

1
2
3
String str = "123";
String str2 = "1"+"23";
System.out.println(str == str2);//true

要将非字面常量字符串存储进常量池,可以调用字符串对象的intern方法
为什么只存储字面常量,其实也不难理解,jvm虚拟机显示存储字面常量是因为字面常量是直接出现在代码里的,是确定,的同时也是一定会用到的,而且有很大概率用来进行多次拼接(循环体代码),所以放进常量池优化内存

jdk1.6 与 jdk1.8的String和常量池

jdk1.6使用(方法区)永久代存储类的静态信息和运行时常量池,字符串常量池也在其中,因为常量池不在堆内存,只有触发fullgc才会回收常量池中对象

jdk1.6字符串常量池内存示意图

jdk1.6字符串常量池内存示意图

jdk1.8字符串常量池内存示意图

jdk1.8字符串常量池移到元数据,元数据在本地内存,同时字符串加入常量池行为发生变化
jdk1.8字符串常量池内存示意图
jdk1.6在调用intern时,如果常量池没有执行的操作是复制字符串在常量池创建一个字符串对象,并返回该对象
jdk1.8在调用intern时,如果常量池没有则在常量池创建一个字符串引用,引用当前字符串对象,返回this

1
2
3
4
5
6
7
8
9
10
11
String str1 = new StringBuilder("hello").append("java").toString();
String str2 = new StringBuilder("hello").append("java").toString();
//此时常量池只有两个字符串hello和java

//jdk1.6的测试效果
System.out.println(str1.intern()==str1);//false
System.out.println(str2==str2.intern());//false

//jdk1.8的测试效果
System.out.println(str1.intern()==str1);//true
System.out.println(str2==str2.intern());//false

使用jol工具进行验证

验证代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
String str1 = new StringBuilder("hello").append("java").toString();
String str2 = new StringBuilder("hello").append("java").toString();

System.out.println(str1.intern()==str1);
System.out.println(str2==str2.intern());

System.out.println("use jol check ...");

String builderString = new StringBuilder("faker").append("大魔王").toString();
System.out.println("-------------------------------------------------------------------------------------------------------");
System.out.println("builderString info");
System.out.println(GraphLayout.parseInstance(builderString).toPrintable());
System.out.println("-------------------------------------------------------------------------------------------------------");

String sin = builderString.intern();
System.out.println("-------------------------------------------------------------------------------------------------------");
System.out.println("init builderString.intern() info");
System.out.println(GraphLayout.parseInstance(sin).toPrintable());
System.out.println("-------------------------------------------------------------------------------------------------------");

String constStr = "faker大魔王";
System.out.println("-------------------------------------------------------------------------------------------------------");
System.out.println("init String constStr = 'faker大魔王' info");
System.out.println(GraphLayout.parseInstance(constStr).toPrintable());
System.out.println("-------------------------------------------------------------------------------------------------------");

String newString = new String("faker大魔王");
System.out.println("-------------------------------------------------------------------------------------------------------");
System.out.println("init new String('faker大魔王') info");
System.out.println(GraphLayout.parseInstance(newString).toPrintable());
System.out.println("-------------------------------------------------------------------------------------------------------");

String newString2 = new String("faker大魔王");
System.out.println("-------------------------------------------------------------------------------------------------------");
System.out.println("init new String('faker大魔王') 2 info");
System.out.println(GraphLayout.parseInstance(newString2).toPrintable());
System.out.println("-------------------------------------------------------------------------------------------------------");

String newStringConcatString = new String("faker")+"大魔王";
System.out.println("-------------------------------------------------------------------------------------------------------");
System.out.println("init String newStringConcatString = new String('faker')+'大魔王' info");
System.out.println(GraphLayout.parseInstance(newStringConcatString).toPrintable());
System.out.println("-------------------------------------------------------------------------------------------------------");

String newStringConcatString2 = new String("faker")+new String("大魔王");
System.out.println("-------------------------------------------------------------------------------------------------------");
System.out.println("init String newStringConcatString2 = new String('faker')+new String('大魔王') info");
System.out.println(GraphLayout.parseInstance(newStringConcatString2).toPrintable());
System.out.println("-------------------------------------------------------------------------------------------------------");

输出结果—jdk1.6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
false
false
use jol check ...
-------------------------------------------------------------------------------------------------------
builderString info
java.lang.String@1b17a8bdd object externals:
ADDRESS SIZE TYPE PATH VALUE
7d5efcbe0 32 java.lang.String (object)
7d5efcc00 32 [C .value [f, a, k, e, r, 大, 魔, 王]


-------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------
init builderString.intern() info
java.lang.String@60765a16d object externals:
ADDRESS SIZE TYPE PATH VALUE
77cfe3860 32 java.lang.String (object)
77cfe3880 32 [C .value [f, a, k, e, r, 大, 魔, 王]


-------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------
init String constStr = 'faker大魔王' info
java.lang.String@60765a16d object externals:
ADDRESS SIZE TYPE PATH VALUE
77cfe3860 32 java.lang.String (object)
77cfe3880 32 [C .value [f, a, k, e, r, 大, 魔, 王]


-------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------
init new String('faker大魔王') info
java.lang.String@5ae80842d object externals:
ADDRESS SIZE TYPE PATH VALUE
77cfe3880 32 [C .value [f, a, k, e, r, 大, 魔, 王]
77cfe38a0 1498006336 (something else) (somewhere else) (something else)
7d647fbe0 32 java.lang.String (object)


-------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------
init new String('faker大魔王') 2 info
java.lang.String@377653aed object externals:
ADDRESS SIZE TYPE PATH VALUE
77cfe3880 32 [C .value [f, a, k, e, r, 大, 魔, 王]
77cfe38a0 1498033856 (something else) (somewhere else) (something else)
7d6486760 32 java.lang.String (object)


-------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------
init String newStringConcatString = new String('faker')+'大魔王' info
java.lang.String@396fe0f4d object externals:
ADDRESS SIZE TYPE PATH VALUE
7d648d358 32 java.lang.String (object)
7d648d378 32 [C .value [f, a, k, e, r, 大, 魔, 王]


-------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------
init String newStringConcatString2 = new String('faker')+new String('大魔王') info
java.lang.String@3ed02b51d object externals:
ADDRESS SIZE TYPE PATH VALUE
7d6492950 32 java.lang.String (object)
7d6492970 32 [C .value [f, a, k, e, r, 大, 魔, 王]


-------------------------------------------------------------------------------------------------------

输出结果—jdk1.8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
true
false
use jol check ...
-------------------------------------------------------------------------------------------------------
builderString info
java.lang.String@1540e19dd object externals:
ADDRESS SIZE TYPE PATH VALUE
d5ff44f8 24 java.lang.String (object)
d5ff4510 32 [C .value [f, a, k, e, r, 大, 魔, 王]


-------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------
init builderString.intern() info
java.lang.String@1540e19dd object externals:
ADDRESS SIZE TYPE PATH VALUE
d5ff44f8 24 java.lang.String (object)
d5ff4510 32 [C .value [f, a, k, e, r, 大, 魔, 王]


-------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------
init String constStr = 'faker大魔王' info
java.lang.String@1540e19dd object externals:
ADDRESS SIZE TYPE PATH VALUE
d5ff44f8 24 java.lang.String (object)
d5ff4510 32 [C .value [f, a, k, e, r, 大, 魔, 王]


-------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------
init new String('faker大魔王') info
java.lang.String@51521cc1d object externals:
ADDRESS SIZE TYPE PATH VALUE
d5ff4510 32 [C .value [f, a, k, e, r, 大, 魔, 王]
d5ff4530 8779704 (something else) (somewhere else) (something else)
d6853ce8 24 java.lang.String (object)


-------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------
init new String('faker大魔王') 2 info
java.lang.String@1b4fb997d object externals:
ADDRESS SIZE TYPE PATH VALUE
d5ff4510 32 [C .value [f, a, k, e, r, 大, 魔, 王]
d5ff4530 8805504 (something else) (somewhere else) (something else)
d685a1b0 24 java.lang.String (object)


-------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------
init String newStringConcatString = new String('faker')+'大魔王' info
java.lang.String@deb6432d object externals:
ADDRESS SIZE TYPE PATH VALUE
d6860660 24 java.lang.String (object)
d6860678 32 [C .value [f, a, k, e, r, 大, 魔, 王]


-------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------
init String newStringConcatString2 = new String('faker')+new String('大魔王') info
java.lang.String@694f9431d object externals:
ADDRESS SIZE TYPE PATH VALUE
d6865708 24 java.lang.String (object)
d6865720 32 [C .value [f, a, k, e, r, 大, 魔, 王]


-------------------------------------------------------------------------------------------------------

String、StringBuffer、StringBuilder

StringBuffer是StringBuilder线程安全版本的实现
所以以下着重讲StringBuilder为什么拼接会比String快,同时节约内存
以及为什么在字符串很短以及拼接次数少时,两者效率差距不大而到了次数多时,差距会变化很大

String中的拼接方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   void getChars(char dst[], int dstBegin) {
System.arraycopy(value, 0, dst, dstBegin, value.length);
}
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);
}
/*
* Package private constructor which shares value array for speed.
* this constructor is always expected to be called with share==true.
* a separate constructor is needed because we already have a public
* String(char[]) constructor that makes a copy of the given char[].
*/
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}

也就是每一次拼接的流程是创建一个长度刚好可以容纳这个拼接后的字符串的新数组,把两者字符复制进去
再来看看StringBuilder的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
   public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}

StringBuilder和String一样在内部维护一个字符数组,但是StringBuilder在构造时会多预留16个位置
StringBuilder会调用父类AbstractStringBuilder的append方法
具体操作为如果当前数组长度不够存储,则将数组长度扩展为原来的两倍+2,如果扩展后还是不够,再直接一次性扩展到刚好满足
这意味着如果每次拼接的字符串相对当前字符串数组要小的多时,因为每次扩容是倍数增长,所以扩容次数相比String会减少
同时直接操作数组除了创建新数组没有创建别的对象,性能也更好(String的内部数组是final不可变,所以每次拼接都是创建新的String对象以及数组对象)
从这里也可以看出,如果每次拼接的字符串长度是指数增长,都要接近甚至更高于数组最新的长度,那么StringBuilder拼接的效率也会下降到和String差不多(当然这种场景是极少见的)

请移步

个人主页: yangyitao.top