String并不是java的基本类型,它是一个对象,但是,创建String是一种“消费行为”(所谓的“消费行为”,就是指该行为是耗时耗内存的行为),而且是一种“误导性的消费行为”,必须防止盲目依赖String,造成不必要的消费。
对于大多数包括我在内的新手而言,使用String似乎是种理所当然的行为,尤其是学过C++的同学,更是庆幸使用String成为内置支持的行为,即使它不是基本类型。但是,使用String并没有我们表面上那么好,凡是“消费行为”,多少都会存在副作用,而String的副作用是非常容易被忽视的,因为我们甚至不曾去探究过(这就是我说它是“误导性”的原因)!String在java中的真正意义是字符串常量,是一个不可被修改的对象。第一次看到这个的时候,我很惊讶:不是可以进行String的连接,像是这样:
String str = "hello";
str += "word";
怎么说是不可被修改的呢?仔细研究就会发现一个惊人的事实,str += "word"这个语句已经创建了新的String了!也就是说,之前的str并没有被修改,因为它的引用的地址和str += "word"创建的字符串"hello word"是不同的。在这短短两行语句中就已经创建了两个完全不同的对象,更别提我以前常干的:
for(int i = 0; i < 10; i++){
str += "..";
}
这简直就是一种灾难!更加可怕的是,我们过去认为应该是同一个String的情况,像是这样:
String str = "hello";
str = "word";
或者:
changeString(str);
public void changeString(String str){
String str = str.replace(...);
}
它们其实都是在创建新的String对象,只是我们以前不知道而已。
没有理解事物的本质就任意的使用,这种行为自古以来就会带来问题。现在,我们已经真正认清楚String的真面目了,是否该开始寻找解决方法呢?就是寻找String的替代品,没有副作用的替代品,StringBuilder和StringBuffer。
这两者的使用都需要使用new,因为它们是显式的真正对象(String其实也是使用new,只是它是隐式的,我们也可以这样创建String对象:String str = new String("hello"))。它们之间的区别就在于StringBuffer多了一个线程安全性(即同步)的附加好处,方便我们在多线程中安全的使用,其余基本一样,所以,这里单纯只是讨论StringBuffer。
StringBuffer与String之间不能进行强制类型转化,但是可以互相转化,像是这样:
String str1 = "hello";
StringBuffer sb = new StringBuffer(str1);
String str2 = sb.toString();
我们使用String,最常用的就是进行+=这样的字符串拼接。StringBuffer的拼接并不像String那样,可以直接使用+,+=,它需要调用方法append(),直接将参数加到字符串的后面,不会产生新的对象。这里要注意,如果是这样:
String str = "hello" + "word";
StringBuffer sb = new StringBuffer("hello").append("word");
使用StringBuffer可能并不会比String快,那是因为在JVM里面,它是这样处理"hello" + "word":一个字符串"hello word",并没有进行+操作,但如果是这样:
String s1 = "hello";
String s2 = "word";
String str = s1 + s2;
String就会显出原形了!
StringBuffer更多提供的是对字符串内容的操作,像是插入,反转等等,这些都可以通过文档查看相关使用方法,这里就讲一个我们平时很少注意到的方法:trimToSize(),它会将StringBuffer对象的中存储空间缩小到和字符串一样的长度,减少空间的浪费。
必须注意的是,StringBuffer的equals()并没有被覆盖,它依然就是Object的equals(),用于比较地址,所以它无法用来进行字符串内容的比较(String就可以)。
StringBuilder,StringBuffer和String三者间的速度比较如下:
StringBuilder > StringBuffer > String.
即使StringBuffer的使用是最安全的,但是大多数情况下是在单线程下使用字符串,不过也不需要担心,因为只有至少百万级别的数量才会体现StringBuilder的速度优势,所以平时这两者爱用谁就用谁。
如果只是少量的数据,使用String是最方便的,但如果是想要编写一个类的toString()方法,这时就要注意,如果这个方法中对字符串的操作是在循环中,使用StringBuffer是最佳的选择。
使用StringBuffer时,如果是处理较大的数据,而且可预见一定会超容,请尽量确定StringBuffer的最大容量,因为StringBuffer的构造器创建的是一个默认大小(通常是16)的字符串数组,若超出,就会重新分配内存,创建一个更大的数组,并将原先的数组复制过来,再丢弃旧的数组。这种操作在已经拥有一定大小的容量时,是很可怕的,所以,创建时就指定不会被超过的容量,编译器会非常感激你的。