字符串类String
String是final类。Java程序中的所有字符串字面值(如”abc”)都作为此类的实例实现。字符串是常量,它们的值在创建之后不能更改,如果对已经存在的String对象进行修改,都是重新new一个对象,然后把修改后的值保存进去。字符串缓存区支持可变的字符串。因为String对象是不可变的,所以可以共享。
字符串的定义很简单,直接给一个字符串类型的变量赋值即可,例如:1
String Str = "abc";
等价于:1
2char data[] = {'a','b','c'}
String str = new String(data);
线程安全的可变字符串类StringBuffer
StringBuffer 是一个线程安全的可变字符串类,通过构造方法创建对象。类似于String的字符串,不同的是它通过某些方法调用改变该序列的长度和内容。它可将字符串缓存区安全地用于多个线程,可以在必要时对这些方法进行同步。1
2StringBuffer(); //构造一个空字符串的字符串缓存区
StringBuffer(String str); //构造一个字符串缓存区,并将其内容初始化为指定的字符串内容
StringBuffer上的主要操作是append()和insert()方法。通过查看StringBuffer类的三段源码,我们会发现最后调用了System.arraycopy来进行来修改字符串。1
2
3
4public synchronized StringBuffer append(String str) {
super.append(str);
return this;
}
1 | public AbstractStringBuilder append(String str) { |
1 | public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { |
可变字符串类StringBuilder
此类提供一个与StringBuffer兼容的API,但不保证同步。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer要快。
如何选择
- String是字符串常量
- StringBuffer是字符串变量(线程安全)
- StringBuilder是字符串变量(非线程安全)
简单地说,String类型和StringBuffer类型的主要性能区别在于: String是不可改变的对象,每次对String类型进行改变的时候,其实都等同于生成了一个新的String对象,然后将引用指向该对象;而对于StringBuffer类,每次操作都是对StringBuffer对象本身进行更改。
所以,如果经常改变内容的字符串最好不要用String,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后,JVM的GC就会开始工作,那速度是一定会相当慢的。这种情况推荐使用StringBuffer,特别是字符串对象经常改变的情况下。但是某些特别情况下,String对象的字符串拼接其实是被JVM解释成了StringBuffer对象的拼接,所以这个时候String对象的速度并不会比StringBuffer对象慢,而特别是以下的字符串对象生成中,String效率是远要比StringBuffer快的:1
2String str = "This is only a " + "simple" + "test";
StringBuffer builder = new StringBuilder("This is only a ").append("simple").append("test");
你会发现,生成str对象的速度明显快多了。其实这是JVM的一个隐藏的实现机制,实际上:1
String str = "This is only a " + "simple" + "test";
其实就是:1
String str = "This is only a simple test";
但是要注意,如果你的字符串是来自另外的String对象,速度就没那么快了,譬如:1
2
3
4String str1 = "This is only a ";
String str2 = "simple";
String str3 = "test";
String str4 = str1+str2+str3;
为了测试这3种类型当累加不同次数字符串时的效率,我们编写一个测试类,分别按次数累加字符串:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44public class TestString {
static int count = 100; //循环次数
//测试String
public static void testString() {
long start = System.nanoTime();
String str = "";
for (int i = 0; i < count; i++) {
str += "," + i;
}
long end = System.nanoTime();
System.out.println("String: " + (end - start));
}
//测试StringBuffer
public static void testStringBuffer() {
long start = System.nanoTime();
StringBuffer str = new StringBuffer();
for (int i = 0; i < count; i++) {
str.append(",").append(i);
}
long end = System.nanoTime();
System.out.println("StringBuffer: " + (end - start));
}
//测试StringBuilder
public static void testStringBuilder() {
long start = System.nanoTime();
StringBuilder str = new StringBuilder();
for (int i = 0; i < count; i++) {
str = str.append(",").append(i);
}
long end = System.nanoTime();
System.out.println("StringBuilder: " + (end - start));
}
public static void main(String[] args) {
TestString.testString();
TestString.testStringBuffer();
TestString.testStringBuilder();
}
}
运行该程序执行的测试时间如表所示:
毫微秒 | String | StringBuffer | StringBuilder |
---|---|---|---|
1次 | 38292 | 32765 | 1579 |
10次 | 52108 | 45792 | 6711 |
100次 | 230143 | 70662 | 64345 |
1000次 | 11072907 | 308305 | 181193 |
1万次 | 400656874 | 892543 | 814777 |
10万次 | 溢出 | 4308762 | 4372318 |
100万次 | 溢出 | 100687270 | 49812689 |
String在10万次循环时就溢出了,而StringBuffer在100万次循环时间为100ms,StringBuilder的时间为49ms。显然选择优先级为: StringBuilder > StringBuffer > String 。因此,对于这3个类的使用,我们需要按照以下情况去选择。
- 如果偶尔对简单的字符串常量进行拼接,那么可以使用String,它足够简单而且轻量级。
- 如果需要经常进行字符串的拼接、累加操作,请使用StringBuffer或者StringBuilder。
- 如果是在单线程的环境中,建议使用StringBuilder,它要比StringBuffer快;如果是在多线程的环境中,建议使用StringBuffer,它是线程安全的。
因此,StringBuilder实际上是我们的首选,只有在多线程时才可以考虑使用StringBuffer,只有在字符串的拼接足够简单时才使用String。