周末在做一道题的时候用了String.format来生成hash值,结果一直运行时间过长,于是就梳理下String相关的知识。
String.format("%09d%0d",i,j);//非常耗时
字符串是否相等
首先是看一个判断String是否相等的问题
String a = "hello";
String b = "hello";
System.out.println(a==b);
// true
在第一次使用hello字符串时,会创建字符串并存入常量池中,重复使用时,就从常量池取出,于是再多的相同字符串的变量都是相等的。
在创建了变量c,使用new String的方式,比较a和c
String c = new String("hello");
System.out.println(a==c);
//false
我们首先是创建了String的引用,然后再将String的引用指向常量池中的“hello”,所以结果是false。在IDE写这条new语句时实际就提示了 'new String' is redundant 这种写法是多余的。
String的类中提供了一个intern方法,注释上说:
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
如果常量池中有这个String相等时就返回,否则将这个String的对象假如到常量池中并返回它的引用。这是一个忽略包装的对象,直接从常量池中取到字符串的方法。
System.out.println(a== c.intern());
//true
运行下代码与预期相符。
字符串拼接问题
实际开发过程中我们经常会拼接一些字符串用于展示。如 单价:25元。在一些开发规范中经常告诉我们不要直接“+”的形式,用StringBuilder效率更高。我们就用代码举个例子试一下。
long startTime = System.currentTimeMillis();
String str = "";
for (int i = 0; i < 10000; i++) {
str += i;
}
System.out.println(System.currentTimeMillis() - startTime);
使用“+”的方式将0~9999共一万个数字进行拼接,时间大约500ms。
再换成StringBuilder的方式
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 10000; i++) {
builder.append(i);
}
只需10ms,效率相差50倍。
我们通过javap查看字节码来研究这个问题。
4 new #3 <java/lang/StringBuilder>
7 dup
8 invokespecial #4 <java/lang/StringBuilder.<init>>
11 astore_3
12 iconst_0
13 istore 4
15 iload 4
17 sipush 10000
20 if_icmpge 36 (+16)
23 aload_3
24 iload 4
26 invokevirtual #5 <java/lang/StringBuilder.append>
29 pop
30 iinc 4 by 1
33 goto 15 (-18)
这是StringBuilder方式生成的字节码,可以看到StringBuilder的创建,调用append方法,和循环(go 15)
接下来看“+”拼接的方式
4 ldc #3 //给String赋值
6 astore_3
7 iconst_0
8 istore 4
10 iload 4
12 sipush 10000
15 if_icmpge 44 (+29)
18 new #4 <java/lang/StringBuilder> //创建StringBuilder
21 dup
22 invokespecial #5 <java/lang/StringBuilder.<init>> //初始化StringBuilder
25 aload_3
26 invokevirtual #6 <java/lang/StringBuilder.append> //扩展str现在的值
29 iload 4
31 invokevirtual #7 <java/lang/StringBuilder.append> //扩展现在的i
34 invokevirtual #8 <java/lang/StringBuilder.toString> //toString转回String
37 astore_3
38 iinc 4 by 1
41 goto 10 (-31)
通过注释可以看出,使用“+”号拼接字符串真是令人窒息的操作,每一次循环都需要创建一个StringBuilder,添加现有值,再append循环的i,最后还要转回String赋值给str。
为什么使用StringBuilder的原因找到了,接下来回到文章开头,看String.format究竟做了什么,本想也看下字节码,看也看不懂。一步步下去找到 \java\util\Formatter.java,大致就是正则找到需要替换的部分,然后循环去替换,而且又需要一堆异常判断就比较耗时。
网友评论