String原理解析

String并不属于基本类型之一,其本质是字符数组。
String在java1.7之前底层是java的字符数组,1.8开始在native层进行处理。
String不可变,对外提供的方法最后会创建新的String对象,不会对原来的String本身造成影响。1.7以前可以通过反射修改String本身,1.8之后不可以。

字符串常量池

为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串常量池,每当以字面值形式创建一个字符串时,JVM会首先检查字符串常量池:如果字符串已经存在池中,就返回池中的实例引用;如果字符串不在池中,就会实例化一个字符串并放到池中。Java能够进行这样的优化是因为字符串是不可 变的,可以不用担心数据冲突进行共享。

手动入池

使用intern方法可以进行手动入池操作,当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。

对于任意两个字符串 s 和 t ,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true 。

1
2
3
4
5
6
String str1 = "abc";
String str2 = new String("abc");
String str3 = s2.intern();
System.out.println( str1 == str2 ); //false
System.out.println( str1 == str3 ); //true

String创建方式的区别

字面值

以字面值方式创建一个String时,会先检查字符串常量池中是否有这个String,如有,则直接返回引用,如果没有,就实例化一个字符串放入池中。

new String(“”)

通过 new String(“…”) 来创建字符串时,在该构造函数的参数值为字符串字面值的前提下,若该字面值不在字符串常量池中,那么会创建两个对象:一个在字符串常量池中,一个在堆中;否则,只会在堆中创建一个对象。

连接符 “+”
1
2
3
4
5
String str2 = "ab"; //1个对象
String str3 = "cd"; //1个对象
String str4 = str2+str3;
String str5 = "abcd";
System.out.println("str4 = str5 : " + (str4==str5)); // false

在运行时,第三行代码(str2+str3)实质上会被分解成五个步骤,分别是:

 (1). 调用 String 类的静态方法 String.valueOf() 将 str2 转换为字符串表示;

 (2). JVM 在堆中创建一个 StringBuilder对象,同时用str2指向转换后的字符串对象进行初始化; 

 (3). 调用StringBuilder对象的append方法完成与str3所指向的字符串对象的合并;

 (4). 调用 StringBuilder 的 toString() 方法在堆中创建一个 String对象;

 (5). 将刚刚生成的String对象的堆地址存赋给局部变量引用str4。

使用连接符 “+” 循环连接大量字符串时,因为会默认创建大量的StringBuilder和String对象,所以会导致性能大幅度下降。

equals

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = length();
if (n == anotherString.length()) {
int i = 0;
while (n-- != 0) {
if (charAt(i) != anotherString.charAt(i))
return false;
i++;
}
return true;
}
}
return false;
}

StringBuffer和StringBuilder

这两者区别在于StringBuffer默认线程同步,StringBuilder不是。

StringBuilder在创建时会创建一个字符数组,append方法会对数组进行扩容,然后将要append的String字符拷贝至数组的末尾,因此不会大量创建String方法,性能较高。

1
2
3
4
5
6
7
8
9
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;
}

String与克隆

在克隆一个类时,其成员变量若是有String,则将String作为基本数据类型看待就好了,因为String本身是不可变的。

小结

  1. 使用字面值形式创建的字符串与通过 new 创建的字符串一定是不同的,因为二者的存储位置不同:前者在方法区,后者在堆;
  2. Java 编译器对 “常量+字面值” 的组合 是当成常量表达式直接求值来优化的;对于含有“String引用”的组合,其在编译期不能被确定,会在运行期创建新对象。
  3. 字符串常量池的理念是 《享元模式》;
  4. Java 编译器对于类似“常量+字面值”的组合,其值在编译的时候就能够被确定了。
文章目录
  1. 1. 字符串常量池
    1. 1.1. 手动入池
  2. 2. String创建方式的区别
    1. 2.1. 字面值
    2. 2.2. new String(“”)
    3. 2.3. 连接符 “+”
  3. 3. equals
  4. 4. StringBuffer和StringBuilder
  5. 5. String与克隆
  6. 6. 小结
|