這篇文章主要講解了“Java字符串的構(gòu)造和拼接”,文中的講解內(nèi)容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Java字符串的構(gòu)造和拼接”吧!
創(chuàng)新互聯(lián)專注于網(wǎng)站建設|網(wǎng)站建設維護|優(yōu)化|托管以及網(wǎng)絡推廣,積累了大量的網(wǎng)站設計與制作經(jīng)驗,為許多企業(yè)提供了網(wǎng)站定制設計服務,案例作品覆蓋公路鉆孔機等行業(yè)。能根據(jù)企業(yè)所處的行業(yè)與銷售的產(chǎn)品,結(jié)合品牌形象的塑造,量身建設品質(zhì)網(wǎng)站。
字符串在Java里是不可變的,無論是構(gòu)造,還是截取,得到的總是一個新字符串??匆幌聵?gòu)造一個字符串源碼
private final char value[]; public String(String original) { this.value = original.value; this.hash = original.hash; }
原有的字符串的value數(shù)組直接通過引用賦值給新的字符串value,也就是倆個字符串共享一個char數(shù)組,因此這種構(gòu)造方法有著最快的構(gòu)造。Java里的String對象被設計為不可變。意思是指一旦程序獲得了字符串對象引用,不必擔心這個字符串在別的地方被修改,不可變意味著線程安全,在第三章對不可變對象線程安全性又說明。
構(gòu)造字符串更多的情況構(gòu)造字符串是通過一個字符串數(shù)組,或者在某些框架的反序列化,使用byte[] 來構(gòu)造字符串,這種情況下性能會非常低。 如下是通過char[]數(shù)組構(gòu)造一個新的字符串源碼
public String(char value[]) { this.value = Arrays.copyOf(value, value.length); }
Arrays.copyOf 會重新拷貝一份新的數(shù)組,方法如下
public static char[] copyOf(char[] original, int newLength) { char[] copy = new char[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
可以看到通過數(shù)組構(gòu)造字符串實際上是會創(chuàng)建一個新的字符串數(shù)組。如果不這樣,還是直接引用char數(shù)組,那么外部如果更改char數(shù)組,則這個新的字符串就被改變了。
char[] cs = new char[]{'a','b'}; String str = new String(cs); cs[0] ='!'
上面的代碼最后一行,修改了cs數(shù)組,但不會影響str。因為str實際上是新的字符串數(shù)組構(gòu)成
通過char數(shù)組構(gòu)造新的字符串是最長用的方法,我們后面看到幾乎每個字符串API,都會調(diào)用這個方法構(gòu)造新的字符串,比如subString,concat等方法。如下代碼驗證了通過字符串構(gòu)造新的字符串,以及使用char數(shù)組構(gòu)造字符串性能比較
String str= "你好,String"; char[] chars = str.toCharArray(); [@Benchmark](https://my.oschina.net/u/3268003) public String string(){ return new String(str); } [@Benchmark](https://my.oschina.net/u/3268003) public String stringByCharArray(){ return new String(chars); }
輸出按照ns/op來輸出,既每次調(diào)用所用的納秒數(shù),可以看到通過char構(gòu)造字符串還是先當耗時的,特別如果是數(shù)組特別長,那更加耗時
Benchmark Mode Score Units c.i.c.c.NewStringTest.string avgt 4.235 ns/op c.i.c.c.NewStringTest.stringByCharArray avgt 11.704 ns/op
通過字節(jié)構(gòu)造字符串,是一種非常常見的情況,尤其現(xiàn)在分布式和微服務流行,字符串在客戶端序列化成字節(jié)數(shù)組,并發(fā)送給你給服務器端,服務器端會有一個反序列化,通過byte構(gòu)造字符串
如下測試使用byte構(gòu)造字符串性能測試
byte[] bs = "你好,String".getBytes("UTF-8"); [@Benchmark](https://my.oschina.net/u/3268003) public String stringByByteArray() throws Exception{ return new String(bs,"UTF-8"); }
測試結(jié)果可以看到byte構(gòu)造字符串太耗時了,尤其是當要構(gòu)造的字符串非常長的時候
Benchmark Mode Score Units c.i.c.c.NewStringTest.string avgt 4.649 ns/op c.i.c.c.NewStringTest.stringByByteArray avgt 82.166 ns/op c.i.c.c.NewStringTest.stringByCharArray avgt 12.138 ns/op
通過字節(jié)數(shù)組構(gòu)造字符串,主要涉及到轉(zhuǎn)碼過程,內(nèi)部會調(diào)用 StringCoding.decode轉(zhuǎn)碼
this.value = StringCoding.decode(charsetName, bytes, offset, length);
charsetName表示字符集,bytes是字節(jié)數(shù)組,offset和length表示字節(jié)數(shù)組
實際負責轉(zhuǎn)碼的是Charset子類,比如sun.nio.cs.UTF_8的decode方法負責實現(xiàn)字節(jié)轉(zhuǎn)碼,如果在深入到這個類,你會發(fā)現(xiàn),你看到的是冰上一角,冰上下面這是一個相當耗CPU計算轉(zhuǎn)碼的工作,屬于無法優(yōu)化的部分.
在我多次的系統(tǒng)性能優(yōu)化過程中,都會發(fā)現(xiàn)通過字節(jié)數(shù)據(jù)組構(gòu)造字符串總是排在消耗CPU比較靠前的位置,轉(zhuǎn)碼消耗的系統(tǒng)性能抵得上百行的業(yè)務代碼。 因此我們系統(tǒng)在設計到分布式的,需要仔細設計需要傳輸?shù)淖侄?,盡量避免用String。比如時間可以用long類型來表示,業(yè)務狀態(tài)也可以用int來表示。如下需要序列化的對象
public class OrderResponse{ //訂單日期,格式'yyyy-MM-dd' private String createDate; //訂單狀態(tài),"0"表示正常 private String status; }
可以改進成更好的定義,以減小序列化和反序列化負擔。
public class OrderResponse{ //訂單日期 private long createDate; //訂單狀態(tài),0表示正常 private int status; }
關(guān)于在微服務中,序列化和反序列化傳輸對象,會在第四章和五章再次介紹對象的序列化
JDK會自動將使用+號做的字符串拼接自動轉(zhuǎn)化為StringBuilder,如下代碼:
String a="hello"; String b ="world " String str=a+b;
虛擬機會編譯成如下代碼
String str = new StringBuilder().append(a).append(b).toString();
如果你運行JMH測試這倆段代碼,性能其實一樣的,因為使用+連接字符串是一個常見操作,虛擬機對如上倆個代碼片段都會做一些優(yōu)化,虛擬使用-XX:+OptimizeStringConcat 打開字符串拼接優(yōu)化,(默認情況下是打開的)。 如果采用以下代碼,雖然看是跟上面的代碼片段差不多,但虛擬機無法識別這種字符串拼接模式,性能會下降很多
StringBuilder sb = new StringBuilder(); sb.append(a); sb.append(b);
運行StringConcatTest類,代碼如下
String a = "select u.id,u.name from user u"; String b=" where u.id=? " ; [@Benchmark](https://my.oschina.net/u/3268003) public String concat(){ String c = a+b; return c ; } [@Benchmark](https://my.oschina.net/u/3268003) public String concatbyOptimizeBuilder(){ String c = new StringBuilder().append(a).append(b).toString(); return c; } @Benchmark public String concatbyBuilder(){ //不會優(yōu)化 StringBuilder sb = new StringBuilder(); sb.append(a); sb.append(b); return sb.toString(); }
有如下結(jié)果說明了虛擬機優(yōu)化起了作用
Benchmark Mode Score Units c.i.c.c.StringConcatTest.concat avgt 25.747 ns/op c.i.c.c.StringConcatTest.concatbyBuilder avgt 90.548 ns/op c.i.c.c.StringConcatTest.concatbyOptimizeBuilder avgt 21.904 ns/op
可以看到concatbyBuilder是最慢的,因為沒有被JVM優(yōu)化
這里說的JVM優(yōu)化,指的是虛擬機JIT優(yōu)化,我們會在第8章JIT優(yōu)化說明
讀者可以自己驗證一下a+b+c這種字符串拼接性能,看一下是否被優(yōu)化了
同StringBuilder類似的還有StringBuffer,主要功能都繼承AbstractStringBuilder, 提供了線程安全方法,比如append方法,使用了synchronized關(guān)鍵字
@Override public synchronized StringBuffer append(String str) { //忽略其他代碼 super.append(str); return this; }
幾乎所有場景字符串拼接都不涉及到線程同步,因此StringBuffer已經(jīng)很少使用了,如上的字符串拼接例子使用StringBuffer,
@Benchmark public String concatbyBuffer(){ StringBuffer sb = new StringBuffer(); sb.append(a); sb.append(b); return sb.toString(); }
輸出如下
Benchmark Mode Score Units c.i.c.c.StringConcatTest.concatbyBuffer avgt 111.417 ns/op c.i.c.c.StringConcatTest.concatbyBuilder avgt 94.758 ns/op
可以看到,StringBuffer拼接性能跟StringBuilder相比性能并不差,這得益于虛擬機的"逃逸分析",也就是JIT在打開逃逸分析情況以及鎖消除的情況下,有可能消除該對象上的使用synchronzied限定的鎖。
逃逸分析 -XX:+DoEscapeAnalysis和 鎖消除-XX:+EliminateLocks,詳情參考本書第8章JIT優(yōu)化
如下是一個鎖消除的例子,對象obj只在方法內(nèi)部使用,因此可以消除synchronized
void foo() { //創(chuàng)建一個對象 Object obj = new Object(); synchronized (obj) { doSomething(); } }
程序不應該依賴JIT的優(yōu)化,盡管打開了逃逸分析和鎖消除,但不能保證所有代碼都會被優(yōu)化,因為鎖消除是在JIT的C2階段優(yōu)化的,作為程序員,應該在無關(guān)線程安全情況下,使用StringBuilder。
使用StringBuilder 拼接其他類型,尤其是數(shù)字類型,則性能會明顯下降,這是因為數(shù)字類型轉(zhuǎn)字符在JDK內(nèi)部,需要做很多工作,一個簡單的Int類型轉(zhuǎn)為字符串,需要至少50行代碼完成。我們在第一章已經(jīng)看到過了,這里不再詳細說明。當你用StringBuilder來拼接字符串,拼接數(shù)字的時候,你需要思考,是否需要一個這樣的字符串。
我們都知道浮點型變量在進行計算的時候會出現(xiàn)丟失精度的問題。如下一段代碼
System.out.println(0.05 + 0.01); System.out.println(1.0 - 0.42);
輸出: 0.060000000000000005 0.5800000000000001
可以看到在Java中進行浮點數(shù)運算的時候,會出現(xiàn)丟失精度的問題。那么我們?nèi)绻谶M行商品價格計算的時候,就會出現(xiàn)問題。很有可能造成我們手中有0.06元,卻無法購買一個0.05元和一個0.01元的商品。因為如上所示,他們兩個的總和為0.060000000000000005。這無疑是一個很嚴重的問題,尤其是當電商網(wǎng)站的并發(fā)量上去的時候,出現(xiàn)的問題將是巨大的??赡軙е聼o法下單,或者對賬出現(xiàn)問題。
通常有倆個方法來解決這種問題,如果能用long來表示賬戶余額以分為單位,這是效率最高的。如果不能,則只能使用BigDecimal類來解決這類問題。
BigDecimal a = new BigDecimal("0.05"); BigDecimal b = new BigDecimal("0.01"); BigDecimal ret = a.add(b); System.out.println(ret.toString());
通過字符串來構(gòu)造BigDecimal,才能保證精度不丟失,如果使用new BigDecimal(0.05),則因為0.05本身精度丟失,使得構(gòu)造出來的BigDecimal也丟失精度。
BigDecimal能保證精度,但計算會有一定性能影響,如下是測試余額計算,用long表示分,用BigDecimal表示元的性能對比
BigDecimal a = new BigDecimal("0.05"); BigDecimal b = new BigDecimal("0.01"); long c = 5; long d = 1; @Benchmark @CompilerControl(CompilerControl.Mode.DONT_INLINE) public long addByLong() { return (c + d); } @Benchmark @CompilerControl(CompilerControl.Mode.DONT_INLINE) public BigDecimal addByBigDecimal() { return a.add(b); }
在我的機器行,上面代碼都能進行精確計算,通過JMH,測試結(jié)果如下
Benchmark Mode Score Units c.i.c.c.BigDecimalTest.addByBigDecimal avgt 8.373 ns/op c.i.c.c.BigDecimalTest.addByLong avgt 2.984 ns/op
感謝各位的閱讀,以上就是“Java字符串的構(gòu)造和拼接”的內(nèi)容了,經(jīng)過本文的學習后,相信大家對Java字符串的構(gòu)造和拼接這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!
分享標題:Java字符串的構(gòu)造和拼接
URL鏈接:http://aaarwkj.com/article16/godddg.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設計公司、網(wǎng)站收錄、自適應網(wǎng)站、定制開發(fā)、微信公眾號、網(wǎng)站導航
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)