Java漫游笔记-02-04-字符串

内部实现

String 类代表字符串。
它是 final 修饰的类,不可继承。

1
public final class String

也就是说,Java 的设计者,不允许任何人定义 String 的子类。
因此,如果有一个 String 的引用,那么它引用的一定是 String 对象。

String 对象是常量,是不可变的,所以可以共享。

1
private final char value[];

反过来想,正是因为字符串的使用频率非常高,所以需要通过一种共享机制来保证性能

String 的 API 比较稳定(1.8 新增了 join() 方法),大部分方法都需要熟练掌握。
但是有一些方法确实容易让人混淆,例如:split()substring()
具体用法,可以参考我编写的 StringApiTest
另外,第三方类库 org.apache.commons.lang3.StringUtilsString 很好的补充,建议使用。

如果要比较字符串的内容是否相等,不用想,直接使用 equals() 或者 equalsIgnoreCase() 就对了。
== 只能用来确定两个字符串是否存放在同一个位置。

创建和存储

关于字符串的创建和存储,我的理解是这样子的:

字符串的创建和存储

第 1 行,在编译期,编译器首先会去字符串池中检查是否存在 "string",如果存在,则把它的内存地址赋给 s1 ,如果不存,则创建后,再返回新的内存地址。
第 2 行,网上很多文章说,会产生 3 个字符串对象,但是,实际上,常量的拼接,编译器是会进行优化的。
第 3 行,因为字符串池中已经存在 "string" ,所以不会创建,但是会在堆中创建一个新的字符串对象 "string"
第 4 行,因为字符串池中不存在 "str" ,所以会创建它,同时,也会在堆中创建一个新的字符串对象 "str"

因此,为了环保,能不 new 就不 new 吧。

字符串拼接

测试结果

字符串拼接测试

结论:
少量字符串拼接,可以直接 +
如果大量的字符串频繁拼接,并且需要保证线程安全,使用 StringBuffer ,否则使用 StringBuilder

解析

首先,假设我们声明了2个字符串变量:

1
2
String s1= "str";
String s2= "ing";

再使用 + 直接把 s1s2 拼接成一个新的字符串 s3

1
String s3= s1+ s2;

实际上,真正执行的代码,大致如下:

1
2
3
4
StringBuilder bulider = new StringBuilder();
bulider.append(String.valueOf(s1));
bulider.append(String.valueOf(s2));
String s3 = bulider.toString();

不难看出,这里额外产生 3 个对象。这就是它低效的原因。

而使用 concat() 方法:

1
String s3= s1.concat(s2);

实际上,会把先把字符串 s1 拷贝到一个足够大的(可以容纳 s1s2 )字符数组 buf ,然后再把拼接的字符串 s2 也拷贝到 buf,再 new 一个新的字符串对象:

1
String s3= new String(buf);

这里的字符串数组拷贝操作需要一定开销,最后还要额外创建新的字符串对象,所以这种方法的效率也不高。
从测试的情况来看,当拼接的字符串较少时,甚至比直接使用 + 拼接还有低效。因此不建议使用。

如果使用 StringBufferappend() 方法:
实际上,会先计算 StringBuffer 的存储空间,如果不足则放大一倍(这也是一个性能优化的点,可以预先分配足够的空间,减少复制开销),再进行字符数组的拷贝。
这种方式,避免了额外的对象创建。这也是它高效的真正原因。

StringBuilderStringBuffer 的实现都是一样的,只不过 StringBuffer 的方法都是 synchronized 的。

测试代码

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
44
45
46
47
48
49
50
51
public class StringPerformanceTest {

private static final int MAX_RUN = 100000;

@Test
public void testUseOperator() {
long start = System.nanoTime();
String str = "";
for (int i = 0; i < MAX_RUN; i++) {
str = str + i;
}
System.out.println("use operator ( + ) = "
+ (System.nanoTime() - start));
}

@Test
public void testUseConcat() {
long start = System.nanoTime();
String str = "";
for (int i = 0; i < MAX_RUN; i++) {
str = str.concat(String.valueOf(i));
}
System.out.println("use String concat() = "
+ (System.nanoTime() - start));
}

@Test
public void testUseStringBuffter() {
long start = System.nanoTime();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < MAX_RUN; i++) {
buffer.append(i);
}
buffer.toString();
System.out.println("use StringBuffer append() = "
+ (System.nanoTime() - start));
}

@Test
public void testUseStringBulider() {
long start = System.nanoTime();
StringBuilder bulider = new StringBuilder();
for (int i = 0; i < MAX_RUN; i++) {
bulider.append(i);
}
bulider.toString();
System.out.println("use StringBuilder append() = "
+ (System.nanoTime() - start));
}

}