简介

jdk8及以前底层用char[]数组实现,jdk9以后改为byte[]。

jdk6及以前,字符串常量池存放在永久代。jdk7及其以后保存在Java堆中。

不可变性

对String字符串重新赋值,拼接,replace指定字符,都不能使用原有value进行赋值。

字符串拼接结果保存

  1. 两个常量拼接结果在常量池,原理是编译器优化。
  2. 要是拼接表达式存在变量,结果就保存在堆中。底层使用String Builder拼接。
  3. 如果拼接结果调用intern()方法,则主动将常量池中还没有的字符串对象存入常量池中,并返回此对象地址。

intern()

图示

image-20201130200723360

代码演示

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
/**
创建两对象
1,堆空间的new 出来的对象
2,字符串常量池的"a"
*/
String s = new String("a");
//由于常量池已经有"a",直接返回常量池"a"地址
String ss = s.intern();
String s2 ="a";
System.out.println(s1 == s2); //jdk6/7/8 : false
System.out.println(ss == s2); //jdk6/7/8 : true

/**
创建"aa"对象,但常量池中只有字符串"a"
**/
String s3 = new String("a") + new String("a");
/**
jdk6
由于此时常量池中无"aa",常量池中存入"aa"字符串(相当于复制一份),返回常量池中字符串的引用地址
所以s3和s4地址不相同
jdk7/8
由于此时常量池中无"aa",所以把s3的对象地址存入常量池(复制的是对象的地址),返回常量池中对象的地址
所以s3和s4的地址是一样的
*/
s3.intern();
String s4 = "aa";
System.out.println(s3 == s4);



字符串常量池

String 的String Pool 不存在相同的内容变量,是固定大小的HashTable,默认长度1009,当里面String过多时,Hash冲突严重,导致链表会很长,String.itern(用于将字符串放入常量池)性能严重下降。

JVM参数

-XX: StringTableSize 可设置JVM的StringTable长度。

  • jdk6:StringTable固定大小,长度就是1009。
  • jdk7:长度默认60013。
  • jdk8:长度默认60013。1009是设置String Table的最小值。

问题!!!

new String(“ab”);new String(“a”) + new String(“b”); 分别创建多少个对象?

new String(“ab”);

两个:

一个是堆中new出来的对象,一个是常量池中的”ab”字符串。

new String(“a”) + new String(“b”);

六个:

  1. StringBulider对象
  2. new String(“a”)堆中new对象
  3. 常量池中的”a”字符串
  4. new String(“b”)堆中new对象
  5. 常量池中的”b”字符串
  6. StringBuilder的toString()方法返回new String(“ab”).注:调用toString()方法常量池不会生成字符串”ab”

扩展String、StringBuilder、StringBuffer区别

String底层是用final修饰的char数组,不可改变,(String的大部分情况都是字母,一个字节即可满足,为降低内存占用,jdk9后改成byte数组,StringBuilder和StringBuffer也是,并用coder字段修饰编码类型)。

1
2
3
//coder字段可选值
@Native static final byte LATIN1 = 0;
@Native static final byte UTF16 = 1;

StringBuilder底层是一个可变的char数组,默认初始化长度为16,构造器有字符串传参时默认长度为字符串长度+16,append操作超过原有长度限制时以下规则扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2; //char数组长度 * 2 + 2
if (newCapacity - minCapacity < 0) {
//扩展后长度还是不满足则直接等于minCapacity(minCapacity = 已使用长度 + append参数的长度)
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError(); //超出最大长度
}
//minCapacity在(Integer.MAX_VALUE - 8, Integer.MAX_VALUE]之间
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}

StringBuffer使用synchronized修饰方法,线程安全,内部维护一个char[] toStringCache数组缓存最近一次toString()的值,每次修改前都置为null。扩展规则和StringBuilder一致,都是使用AbstractStringBuilder抽象类的方法。

1
2
3
4
5
6
7
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}