String是不可变的,StringBuffer是可变的。
String s = "123";
System.out.println("s = " + s);
s="456";
System.out.println("s = " + s);
运行结果:
s = 123
s = 456
初学者看到这段代码,都觉得 String 明明是可变的嘛,下面放一张图解释一下:
s="123"
走到 s="456"时
只不过是指向发生了变化 所以 打印的时候 才会打印出 456, 而 123 一旦生成了 是无法改变的。
看一下源码实现 我的版本是 JDK1.8.0_73
String 本身是一个 字符数组,用 final 修饰 意味着初始化后不可改变,但是 数组里面的值是可以修改赋值的,使用了private 关键字 不提供对外修改的方法,让它无法改变。
String 中的方法 concat,replace,substring 都返回的是一个新的String对象,相当于改变了箭头的指向,如上图所示
String 中连接一个字符串到尾部:
返回一个新的对象。
String s = "123";
System.out.println("s = " + s);
s.concat("4");
System.out.println("s = " + s);
运行结果:
s = 123
s = 123
StringBuffer是可变字符串:
String s = "123";
StringBuffer sb=new StringBuffer(s);
System.out.println("sb = " + sb.toString());
sb.append('4');
System.out.println("sb = " + sb.toString());
运行结果:
sb = 123
sb = 1234
查看源码:
StringBuffer 也是使用一个字符数组进行存储,不过它没有 final和private 进行修饰。
调用append('4')的方法是 本身调用父类的方法对 value 字符数组进行修改
不可变好处:
1. 只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现
2. 数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
3. 因为字符串是不可变的,所以是多线程共享安全的,同一个字符串实例可以被多个线程共享。
4. 因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串
5.类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。
坏处:影响效率,对系统性能产生影响。 特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。
StringBuffer和StringBuilder的区别:
StringBuffer是线程安全的,StringBuilder不是线程安全的
他们都是继承的同一个父类,方法的实现基本没有区别,只不过是StringBuffer中的每个方法都加个同步锁 synchronized关键字用来保证线程安全。