对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象。
Java为了避免在一个系统中产生大量的String对象,引入了字符串常量池。其运行机制是:创建一个字符串对象,如果有则不需要创建直接从池中查找到的对象引用;如果没有则新建字符串对象,返回对象引用,并且将新创建的对象放入池中。但是,通过new方法创建的String对象是不检查字符串池的,而是直接在堆区或栈区创建一个新的对象,也不会把对象放入池中。举例:String str1 = “123”;//通过直接赋值方式,放入字符串常量池
String str2 = new String(“123”);//通过new方式赋值方式,不放入字符串常量池
String的特性:
[A] 不可变,是指String对象一旦生成,则不能再对它进行改变。不可变的作用主要在于当一个对象需要被多线程共享,并且访问频繁时,可以省略同步和锁等待的时间,从而大幅度提高系统性能。不可变模式是一个可以提高多线程程序的性能,降低多线程程序复杂度的设计模式。
[B] 针对常量池的优化。当2个String对象拥有相同的值时,他们只引用常量池中的同一个拷贝。当同一个字符串反复出现时,这个技术可以大幅度节省内存空间。
不可变的好处
1. 可以缓存 hash 值
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
2. String Pool 的需要
如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
3. 安全性
String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。
4. 线程安全
String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
String类型和StringBuilder类型的主要性能区别其实在于String是不可变的对象,因此在每次对String类型进行改变的时候其实都等同于生成了一个新的String对象,然后将指针指向新的String对象,所以经常改变内容的字符串最好不要用String,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后,JVM的GC就会开始工作,那速度是一定会相当慢的。而如果是使用StringBuilder类则结果就不一样了,每次结果都会对StringBuilder对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用StringBuilder,特别是字符串对象经常改变的情况下。
StringBuilder和StringBuffer类拥有的成员属性以及成员方法基本相同,拥有几乎一致对外提供的调用接口,其底层在内存中的存储方式与String相同,都是以一个有序的字符序列(char类型的数组)进行存储,不同点是StringBuilder/StringBuffer对象的值是可以改变的,并且改变以后对象的引用不会发生改变。两者对象在构造过程中,首先按照默认大小申请一个字符数组,由于会不断加入新数据,当超过默认大小后,会创建一个更大的数组,再丢弃旧的数据。因此,对于较大的对象的扩容会涉及大量的内存复制操作,如果能够预先评估大小,可以提升性能。区别是StringBuffer类的成员方法前面多了一个关键字:synchronized,不用多说,这个关键字是在多线程访问时起到安全保护作用的,也就是说StringBuffer是线程安全的。
String, StringBuffer and StringBuilder:
1. 可变性
String 不可变
StringBuffer 和 StringBuilder 可变
2. 线程安全
String 不可变,因此是线程安全的
StringBuilder 不是线程安全的
StringBuffer 是线程安全的,内部使用 synchronized 进行同步
应用场景:
[A] 字符串内容不经常发生变化的业务场景优先使用String类。例如:常量声明、少量的字符串拼接操作等。
[B] 在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在多线程环境下,建议使用StringBuffer,例如XML解析、HTTP参数解析与封装。
[C] 在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在单线程环境下,建议使用StringBuilder,例如SQL语句拼装、JSON封装等。
String自身的演化:
在没有线程安全问题的情况下,全部拼接操作是应该都用StringBuilder吗?毕竟这样书写的代码,还是要多敲很多字的,可读性也不理想,下面的对比非常明显。
String strByBuilder = newSrtingBuilder().append(‘aa’).append(‘bb’).append(‘cc’).append(‘dd’).toString();
String strByConcat = ‘aa’ + ‘bb’ + ‘cc’ + ‘dd’;
通过反编译可以看到,在JDK8中,字符串拼接操作会自动被javac转换为StringBuilder操作,而在JDK9里面则是因为Java9为了更加统一字符串操作优化,提供了StringConcatFactory,作为一个统一的入口。所以在JDK8之后可以直接使用String类型。
Java的字符串在历史版本中,是使用char数组来存数据的,这样非常直接。但是Java中的char是两个bytes大小,拉丁语系语言的字符,根本就不需要太宽的char,这样无区别的实现就造成了一定的浪费。在Java9中,引入了Compact Strings的设计,对字符串进行了大刀阔斧的改进。将数据存储方式从char数组,改变为一个byte数组加上一个标识编码的所谓coder,并且将相关字符串操作类都进行了修改。原来char数组的实现,字符串的最大长度就是数组本身的长度限制,但是替换成byte数组,同样数组长度下,存储能力是原来的一半。