周末在做一道題的時(shí)候用了String.format來生成hash值踩寇,結(jié)果一直運(yùn)行時(shí)間過長窑业,于是就梳理下String相關(guān)的知識士修。
String.format("%09d%0d",i,j);//非常耗時(shí)
字符串是否相等
首先是看一個(gè)判斷String是否相等的問題
String a = "hello";
String b = "hello";
System.out.println(a==b);
// true
在第一次使用hello字符串時(shí)智什,會創(chuàng)建字符串并存入常量池中持灰,重復(fù)使用時(shí),就從常量池取出怎燥,于是再多的相同字符串的變量都是相等的瘫筐。
在創(chuàng)建了變量c,使用new String的方式铐姚,比較a和c
String c = new String("hello");
System.out.println(a==c);
//false
我們首先是創(chuàng)建了String的引用策肝,然后再將String的引用指向常量池中的“hello”,所以結(jié)果是false隐绵。在IDE寫這條new語句時(shí)實(shí)際就提示了 'new String' is redundant 這種寫法是多余的之众。
String的類中提供了一個(gè)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.
如果常量池中有這個(gè)String相等時(shí)就返回依许,否則將這個(gè)String的對象假如到常量池中并返回它的引用棺禾。這是一個(gè)忽略包裝的對象,直接從常量池中取到字符串的方法峭跳。
System.out.println(a== c.intern());
//true
運(yùn)行下代碼與預(yù)期相符膘婶。
字符串拼接問題
實(shí)際開發(fā)過程中我們經(jīng)常會拼接一些字符串用于展示。如 單價(jià):25元蛀醉。在一些開發(fā)規(guī)范中經(jīng)常告訴我們不要直接“+”的形式悬襟,用StringBuilder效率更高。我們就用代碼舉個(gè)例子試一下拯刁。
long startTime = System.currentTimeMillis();
String str = "";
for (int i = 0; i < 10000; i++) {
str += i;
}
System.out.println(System.currentTimeMillis() - startTime);
使用“+”的方式將0~9999共一萬個(gè)數(shù)字進(jìn)行拼接脊岳,時(shí)間大約500ms。
再換成StringBuilder的方式
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 10000; i++) {
builder.append(i);
}
只需10ms垛玻,效率相差50倍逸绎。
我們通過javap查看字節(jié)碼來研究這個(gè)問題。
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方式生成的字節(jié)碼夭谤,可以看到StringBuilder的創(chuàng)建棺牧,調(diào)用append方法,和循環(huán)(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> //創(chuàng)建StringBuilder
21 dup
22 invokespecial #5 <java/lang/StringBuilder.<init>> //初始化StringBuilder
25 aload_3
26 invokevirtual #6 <java/lang/StringBuilder.append> //擴(kuò)展str現(xiàn)在的值
29 iload 4
31 invokevirtual #7 <java/lang/StringBuilder.append> //擴(kuò)展現(xiàn)在的i
34 invokevirtual #8 <java/lang/StringBuilder.toString> //toString轉(zhuǎn)回String
37 astore_3
38 iinc 4 by 1
41 goto 10 (-31)
通過注釋可以看出朗儒,使用“+”號拼接字符串真是令人窒息的操作颊乘,每一次循環(huán)都需要?jiǎng)?chuàng)建一個(gè)StringBuilder,添加現(xiàn)有值醉锄,再append循環(huán)的i乏悄,最后還要轉(zhuǎn)回String賦值給str。
為什么使用StringBuilder的原因找到了恳不,接下來回到文章開頭檩小,看String.format究竟做了什么,本想也看下字節(jié)碼烟勋,看也看不懂规求。一步步下去找到 \java\util\Formatter.java筐付,大致就是正則找到需要替換的部分,然后循環(huán)去替換阻肿,而且又需要一堆異常判斷就比較耗時(shí)瓦戚。