String 为什么不可变:
- 保存字符串的数组被 final 修饰且为私有的,并且 String 类没有提供/暴露修改这个字符串的方法。
- String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
不可变的好处:
-
可以缓存 hash 值
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
-
字符串常量池的需要
字符串池的实现有一个前提条件:String对象是不可变的。因为这样可以保证多个引用可以同时指向字符串池中的同一个对象。如果字符串是可变的,那么一个引用操作改变了对象的值,对其他引用会有影响,这样显然是不合理的。
-
线程安全
String 不可变性天生具备线程安全,可以在多个线程中安全地使用(因为没有一个线程可以修改其 内部状态和数据,且其内部状态和数据也不会自行发生改变)。设置成final,可能子类会破坏方法内的行为。
String.intern():
-
String.intern() 方法可以使得所有含相同内容的字符串都共享同一个内存对象,可以节省内存空间。
JVM 中,存在一个字符串常量池,字符串的值都存放在这个池中。当调用 intern 方法时,如果字符串常量池中已经存在该字符串,那么返回池中的字符串引用;否则将此字符串添加到字符串常量池中,并返回字符串的引用。
String s1 = new String(“123”)和String s2 = "123"的区别:
-
两个语句都会去字符常量池中检查是否已经存在"123",如果有则直接返回池中对象引用地址,没有则会在常量池中创建"123"对象。
-
不同的是,String s1 = new String(“123”)还会在堆里创建一个"123"字符串对象的实例,返回堆中对象的地址。
【采用字面值创建一个字符串对象】
JVM首先会去字符串池中查找是否存在"aaa"这个对象,如果不存在,则在字符串池中创建"aaa"这个对象,然后将池中"aaa"这个对象的引用地址返回给字符串常量str,这样str会指向池中"aaa"这个字符串对象;如果存在,则不创建任何对象,直接将池中"aaa"这个对象的地址返回,赋给字符串常量。
【采用new关键字创建一个字符串对象】
JVM首先在字符串常量池中查找有没有"aaa"这个字符串对象,如果有,则不在池中再去创建"aaa"这个对象了,直接在堆中创建一个"aaa"字符串对象,然后将堆中的这个"aaa"对象的地址返回赋给引用str1,这样,str1就指向了堆中创建的这个"aaa"字符串对象;如果没有,则首先在字符串常量池池中创建一个"aaa"字符串对象,然后再在堆中创建一个"aaa"字符串对象,然后将堆中这个"aaa"字符串对象的地址返回赋给str1引用,这样,str1指向了堆中创建的这个"aaa"字符串对象。
String s1 = new String(“123”)创建了几个对象:
一个或两个。如果字符串常量池已经有"123",则是一个,否则,两个:
- 一个是字符串"123"所对应的字符串常量池中的实例
- 另一个是通过new String创建并初始化的,在堆中,内容指向字符串常量池
String s1 = "a";
:StringTable[“a”]。在常量池中查找有没有"a" 这个对象,如果有,就让s1指向那个"a".如果没有,在常量池中新建一个“aaa”对象,并让s1指向在常量池中新建的对象"a"。String s2 = "b";
// StringTable[“a”, “b”]。String s3 = 'ab';
// StringTable[“a”, “b”, “ab”]。String s4 = s1 + s2;
// 两变量拼接,在堆里。= new StringBuilder().append(“a”).append(“b”).toString() -> new String(“ab”);String s5 = "a" + "b";
// “ab”。两常量拼接,在串池中。javac在编译期间优化,结果已经在编译期确定为ab。String s6 = s4.intern()
//常量池中有对象则返回常量池中的对象,没有则s4入池。sout(s3 == s4);
// false:s4是在堆里new了一个对象,s3在在串池 String table 中。sout(s3 == s5);
// true,s5引用常量池中已有的对象。sout(s3 == s6);
// true。String a2 = s1 + "b";
//= new StringBuilder().append(s1).append(“b”).toString();new String(“ab”)。String a1 = new String("a");
//在堆中建立的对象"a" ,在栈中创建堆中"a" 对象的内存地址。String x2 = new String("c") + new String("d");
// new String(“cd”)String x1 = "cd";
// “cd”x2.intern();
//入池失败。sout(x1 == x2);
//false
String、StringBuffer 和 StringBuilder 的区别:
- String:String类使用final修饰的,被创建后不能被修改,任何对String的修改都会引发新的String对象的生成
- StringBuffer:和String类似,但是值可以被修改的,使用synchronized保证线程的安全
- StringBuilder:String的非线程安全版本,性能上更高一些
性能:String < StringBuffer < StringBuilder
- String 类中使用 final 关键字修饰字符数组来保存字符串,
private final byte[] value
,所以String对象是不可变的。- StringBuilder 与 StringBuffer 都继承⾃ AbstractStringBuilder 类,在 AbstractStringBuilder 中 也是使用字符数组保存字符串
char[] value
但是没有用 final 关键字修饰,所以这两种对象都是可变的。- StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的