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 。
String创建方式的区别
字面值
以字面值方式创建一个String时,会先检查字符串常量池中是否有这个String,如有,则直接返回引用,如果没有,就实例化一个字符串放入池中。
new String(“”)
通过 new String(“…”) 来创建字符串时,在该构造函数的参数值为字符串字面值的前提下,若该字面值不在字符串常量池中,那么会创建两个对象:一个在字符串常量池中,一个在堆中;否则,只会在堆中创建一个对象。
连接符 “+”
|
|
在运行时,第三行代码(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
|
|
StringBuffer和StringBuilder
这两者区别在于StringBuffer默认线程同步,StringBuilder不是。
StringBuilder在创建时会创建一个字符数组,append方法会对数组进行扩容,然后将要append的String字符拷贝至数组的末尾,因此不会大量创建String方法,性能较高。
String与克隆
在克隆一个类时,其成员变量若是有String,则将String作为基本数据类型看待就好了,因为String本身是不可变的。
小结
- 使用字面值形式创建的字符串与通过 new 创建的字符串一定是不同的,因为二者的存储位置不同:前者在方法区,后者在堆;
- Java 编译器对 “常量+字面值” 的组合 是当成常量表达式直接求值来优化的;对于含有“String引用”的组合,其在编译期不能被确定,会在运行期创建新对象。
- 字符串常量池的理念是 《享元模式》;
- Java 编译器对于类似“常量+字面值”的组合,其值在编译的时候就能够被确定了。