共计 1492 个字符,预计需要花费 4 分钟才能阅读完成。
字符串在程序中的使用率很高,每种程序语言都对字符串做了非凡解决。Java 中的字符串是以对象存在的,且底层有多个性。
一、String 的个性
String 最大的个性是不可变,底层用一个数组来存储对应的值:
private final byte[] value;
能够看到,变量用 final 进行润饰,来防止援用的批改,并用 private 来让别的类无奈间接应用,这里只保障了援用的不可变,如何保障值的不可变呢?
其实,值的须要从实现上来保障,String 中将所有引起 value 扭转的办法,都新生成一个对象,例如拼接,裁剪等操作,都会 copy 一份再进行操作。
甚至,类也用 final 进行润饰,确保没有子类继承 String,进而对 value 进行批改。
来看个例子,上面的代码执行后会输入 “aaa”,还是 “bbb”?
public static void main(String[] args) {
String s = "aaa";
change(s);
System.out.println(s);
}
public static void change(String s) {s = "bbb";}
这里的最终后果是会输入 “aaa”,因为赋值的时候,不是扭转了 s 的 value 值,而是扭转了 s 的援用,而援用是办法的形参,改了并不影响调用方,所以输入的是 “aaa”。
二、常量池
为何 String 的设计上肯定要不可变呢?
因为字符串常量池的存在,常量池的实现,是因为大多零碎上用的字符串大体是雷同的,因而把罕用的字符串缓存起来,放到一个池子外面,这样就能够进行复用,防止频繁创建对象带来的损耗。
如果 String 是可变的,常量池便无奈实现,如下,如果 s1 扭转了 “ccc” 的值,会影响到 s2 的应用。
常量池除了缓存罕用的字符串,还提供了 inter() 办法来让业务方本人增加须要的值到 pool 外面。
这里要留神下,Java 6 因为把常量池放到了永恒代外面(PermGen),所以不倡议频繁应用 inter,否则容易引发 OOM,后续的版本将常量池放到了堆中治理,解决了该问题。
但手动调用 inter 还是有肯定老本,不仅净化了业务代码,而且通常应用的时候无奈预测哪些须要进行缓存,在 G1 GC 的 Intern 机制里,间接在 JVM 层面做了优化,来确保雷同字符串的援用是雷同的。
三、不可变的魅力
不可变的长处还有哪些呢?
首先,是减少了代码的可读性,代码很大水平上来说是用来保护的,这样的设计更可能传播自身所具备的性质;
其次,是进行束缚,防止有意无意扭转值带来的问题;
再者,人造反对并发场景,因为不可变,所以没有线程平安的问题;
最初,是性能方面,除了能够反对常量池这样的设计,值得一提的还有 hashCode 的值也能够在创立之初就进行缓存,也是因为该个性,所以 String 特地适宜做 Map 的 key 来应用。
四、StringBuilder 和 StringBuffer
尽管对罕用的字符串进行了优化,但业务上如果字符串操作太多,也会产生太多的两头对象。StringBuilder 和 StringBuffer 的设计便是解决这类问题。
它们都继承了 AbstractStringBuilder,类外面有个可变的成员变量 byte[] value。
两者的区别在于 StringBuffer 的办法上都加了 synchronized 来反对多线程的场景。
五、利用
业务上,对字符串的操作较少的,能够用 String 类,例如一些常量的应用,或者简略的存储业务须要的值;
操作频繁,包含裁剪,拼接,替换等,并且有线程平安需要的,须要应用 StringBuffer,例如网关等场景;
操作频繁,但没有线程平安的要求,就应用 StringBuilder。