String不可变说明
final说明
- 修饰类,标识该类不能被继承,该类的所有方法自动成为final方法
- 修饰方法,方法不能被重写
- 修饰基本数据类型,表示为常量,值不能修改
- 修饰引用类型变量,标识引用地址不可变,但是引用地址对象的属性可以改变
String部分源码
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
String类被final修饰,不能被重写,value被final修饰,不可变。并且没有setter方法,保证String不可变。
可以通过反射拿到value对象,进行改变。
String设置不可变的原因
- 和运行时常量池字符串创建过程有关:
String s = "abc"
类似这种赋值时,先查看字符串常量池中是否有abc,如果有,直接将s指向abc,如果没有则新建一个abc,将s指向Sting类中的hash字段,因为不可变,hashcode唯一,可以使用hash进行缓存,不用每次都计算。 - 安全性
identityHashCode()返回对象物理地址的hashcode;hashCode()返回重写后的hashCode。如果没重写两个一样。
String 内存分配
- 对于直接双引号创建的字符串,jdk1.6以前放在永久代中,jdk1.7以后放在堆中。原因:永久代较小,放太多的话会OOM,另外永久代垃圾回收效率低,频率低,但String使用较多。
- 对于String s2=new String("abc)创建的,如果常量池中存在"abc",则先会在堆中 创建s2对象,然后将s2的地址指向常量池中存在的常量;如果常量池中本身没有"abc",在堆中创建一个String对象,并赋值给s2;当调用s2=s2.intern()之后,会将s2指向常量池(见下文分析)。
StringTable
字符串常量池中是不会存储相同字符串的。·String底层存储是一个固定大小的HashTable
new String()创建了几个对象?
2个,一个对象,一个字符串常量池的,一个堆空间的
toString()不会再字符串常量池中创建。
String s1 = new String("a") + new String("b");字符串常量池中并不会有ab。
String 字符串拼接原理
String str1 = "a";
String str2 = new String("b");
String str3=str1+str2;
//创建一个StringBuilder
StringBuilder sb = new StringBuilder();
sb.append(str1);
sb.append(str2);
//toString返回return new String(value, 0, count);
str3 = sb.toString();
对于String str3=str1+str2;
其实相当于建了一个StringBuilder对象进行拼接,最后再调用sb.toString()
方法返回一个新的String对象。因此使用加号拼接的话会创建多个StringBuilder
对象和String
对象。
如果两个都是常量或者常量引用,则会编译期优化。
intern()方法
JDK1.6中:从字符串常量池中查询是否存在该字符串,如果存在,直接返回常量池字符串地址,不存在则在常量池中创建。并返回常量池地址。
JDK1.8中:从字符串常量池中查询是否存在该字符串,如果存在,直接返回常量池字符串地址,如果不存在,则会在常量池中创建,并把对象地址引用复制一份,放入常量池中,返回对象地址引用。(原因是1.8中字符串常量池在堆中,避免重复创建,浪费空间)
1、如果字符串常量池中本身存在进行intern();
public static void main(String[] args) {
String a1="abc";
String a2= new String("abc");
System.out.println(a1==a2);//false,一个指向对象,一个指向常量池
a2=a2.intern();
System.out.println(a1==a2);//true,2个都指向常量池
}
下面演示常量池中没有的情况下进行intern();
String s1 = new String("a") + new String("b");
String s2=s1.intern();
System.out.println(s1 =="ab");//输出true
System.out.println(s2 =="ab");//输出true
3、intern()之后不赋值情况
String s1 = new String("a") + new String("b");
s1.intern();
String s2="abc";
System.out.println(s1 =="ab");//输出true
疑问: String s2="abc";到底s2是啥?常量池对象的地址还是堆中字符串对象的的引用?
解答:
常量池:字面量和符号引用
运行时常量池:方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池(Constant Pool Table),存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池。
全局字符串常量池:
HotSpot VM里,记录了一个全局表叫做StringTable,它本质上就是个HashSet<String>。这是个纯运行时的结构,而且是惰性(lazy)维护的。注意它只存储对java.lang.String实例的引用。
一般我们说一个字符串进入了全局的字符串常量池其实是说在这个StringTable中保存了对它的引用,反之,如果说没有在其中就是说StringTable中没有对它的引用。
所以常量池中放的并不是真正的String对象,而是对象的引用。String s2="abc"。s2返回的时堆中abc对象的引用。