一、概念
1、String是Java语言中非常重要和基础的类,所有在Java程序中的字符串字面量,比如
"abc"
,String类是一个典型的不可变类,被声明成为final class,所有属性也是final的,创建出来之后不能再修改,所以类似下面这两端代码是等价的,都会创建"abc"字符串:
String str = "abc";
char data[] = {'a', 'b', 'c'};
String str = new String(data);
String构造函数不能传入null值,否则会报空指针的错误:
String nullStr = null;
String s5 = new String(nullStr);
同时由于它的不可变性,类似拼接、裁剪字符串等动作都会产生新的String对象。
由于字符串操作的普遍性,所以相关操作的效率往往对应用性能会有影响。
2、StringBuffer是一个线程安全的、支持可变字符的序列。StringBuffer与String非常像,但是它的字符序列是可修改的,可以解决String拼接产生太多中间对象的问题而提供的一个类,我们可以使用append或者add方法,把字符串添加到已有序列的末尾或者指定位置,它保证了线程安全,但是也随之带来了额外的性能开销,因为StringBuffer的线程安全是通过synchronized关键字来保证的,所以除非有线程安全的需要,不然还是尽量使用它的后继者,也就是StringBuilder。每一个StringBuffer有一个容量,字符序列的长度不能超过这个容量。不能准确预估容量时一般不要来指定长度,因为StringBuffer会自动进行扩容,默认的初始容量为16:
/**
* Constructs a string buffer with no characters in it and an
* initial capacity of 16 characters.
*/
public StringBuffer() {
super(16);
}
。如果确定程序会进行很多次拼接,而且大概是可预估的,那么就可以指定合适的大小,避免多次扩容的开销。扩容会产生多重开销,因为要抛弃原有的数组,创建新的数组,还要进行arrayCopy。
3、StringBuilder是在JDK1.5中新增的,在能力上与StringBuffer并没有本质的区别,可以看到他们都是继承了java.lang.AbstractStringBuilder这个类,但是它去掉了线程安全的部分,有效减小了开销,在绝大部分情况下是字符串拼接的首选。
二、字符串缓存
我们粗略统计过,把常见的应用进行堆转储(Dump Heap),然后分析对象组成,会发现平均25%的对象时字符串,并且其中约半数是重复的。如果能避免创建重复字符串,可以有效降低消耗和对象创建开销。
String在JDK1.6之后提供了intern()方法,目的是提示JVM把相应字符串缓存起来,以备重复使用。我们咋调用intern方法的时候,如果字符串常量池里已经有缓存的字符串就会返回缓存里的实例,否则将其缓存起来并返回,判断是否相等使用equals()方法判断,当且仅当
s.equals(t)==true
s.intern() == t.intern()==true
并且所有所有文本字符串和字符串值常量表达式都是会被缓存的。一般使用Java6这种历史版本,并不推荐大量使用intern,因为被缓存的字符串是保存在PermGen(永久代),这个空间是有限的,也基本不会被FullGC之外的垃圾收集光顾到,一旦使用不当就会发生OOM。在JDK后续版本中,这个缓存被放置在堆中,这样就极大避免了永久代被占满的问题,甚至永久代在JDK8中被MetaSpace(元数据区)替代了。而且默认缓存区大小也在不断的扩大中,从最初的1009到7u40以后被修改为60013。可以使用下面的参数直接打印具体数字:
-XX:+PrintStringTableStatistics
也可以使用下面的JVM参数手动调整大小,但是绝大部分情况下并不需要调整,除非能够确定它的大小已经影响到了操作效率。
-XX:StringTableSize=N
intern是一种显式的排重机制,但是它也有一定的副作用,因为需要开发者写代码的时候手动调用,一是不方便,每一个显式调用是非常麻烦的,另外就是很难来保证效率,应用开发阶段很难预估到字符串的重复情况,也有人认为这是一种污染代码的实践。幸好在Oracle JDK 8u20之后,推出了一个新的特性,也就是G1 GC下的字符串排重。它是通过将相同数据的字符串指向同一份数据来做到的,是JVM底层的改变,并不需要Java类库做修改。这个功能目前默认是关闭的,需要使用下面的参数开启,并且需要指定使用G1 GC:
-XX:+UseStringDeduplication
三、String自身的演化
Java的字符串在历史版本中是使用char数组来存放数据的,这样非常直接。但是Java中的char是两个byte大小,拉丁语系语言的字符不需要太宽的char,这样无区别的实现造成了一定的资源浪费。密度是编程语言平台永恒的话题,因为归根结底绝大部分任务是用来操作数据的。
在JDK 9中引入了Compact strings的设计,对字符串进行了大刀阔斧的改进。将数据存储方式从char数组改变为一个byte数组加上一个标识编码的所谓coder,并且将相关字符串操作类进行了修改。虽然底层发生了这么大的改变,但是Java字符串的行为并没有任何大的变化,这个特性对于绝大部分应用来说都是透明的,绝大部分情况下不需要修改已有的代码。可能在极端情况下字符串能的能力也出现了一些能力退化,比如最大字符串的大小。原来char数组的实现,字符串的最大长度就是数组本身的长度限制,但是替换成了byte数组,同样数组长度下,存储能力是退化了一倍的。在通用的性能测试和产品实验中,我们能非常明显的看到紧凑字符串带来的优势,即更小的内存占用、更快的操作速度。
四、getBytes和String相关的转换时最好根据业务需要建议指定编码方式,如果不指定则看JVM参数里有没有指定file.encoding参数,如果JVM没有指定,那使用的默认编码就是运行的操作系统环境的编码了,那这个编码就变得不确定了。常见的编码ISO8859-1是单字节编码,UTF-8是变长的编码。