自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

String 既然能這樣性能調(diào)優(yōu),我直呼內(nèi)行

開發(fā) 前端
今天給大家見識一下不一樣的 String,從根上拿捏直達(dá) G 點。String對象是我們每天都「摸」的對象類型,但是她的性能問題我們卻總是忽略。

[[431073]]

莫慌,今天給大家見識一下不一樣的 String,從根上拿捏直達(dá) G 點。

并且碼哥分享一個例子:通過性能調(diào)優(yōu)我們能實現(xiàn)百兆內(nèi)存輕松存儲幾十 G 數(shù)據(jù)。

String對象是我們每天都「摸」的對象類型,但是她的性能問題我們卻總是忽略。

愛她,不能只會簡單一起玩耍,要深入了解String 的內(nèi)心深處,做一個「心有猛虎,細(xì)嗅薔薇」的暖男。

通過以下幾點分析,我們一步步揭開她的衣裳,直達(dá)內(nèi)心深處,提升一個 Level,讓 String 直接起飛:

  1. 字符串對象的特性;
  2. String 的不可變性;
  3. 大字符串構(gòu)建技巧;
  4. String.intern 節(jié)省內(nèi)存;
  5. 字符串分割技巧

String 身體解密

想要深入了解,就先從基本組成開始……

「String 締造者」對 String 對象做了大量優(yōu)化來節(jié)省內(nèi)存,從而提升 String 的性能:

Java 6 及之前

數(shù)據(jù)存儲在 char[]數(shù)組中,String通過 offset 和 count兩個屬性定位 char[] 數(shù)據(jù)獲取字符串。

這樣可以高效快速的定位并共享數(shù)組對象,并且節(jié)省內(nèi)存,但是有可能導(dǎo)致內(nèi)存泄漏。

共享 char 數(shù)組為啥可能會導(dǎo)致內(nèi)存泄漏呢?

  1. String(int offset, int countchar value[]) { 
  2.     this.value = value; 
  3.     this.offset = offset; 
  4.     this.count = count
  5.  
  6. public String substring(int beginIndex, int endIndex) { 
  7.     //check boundary 
  8.     return  new String(offset + beginIndex, endIndex - beginIndex, value); 

調(diào)用 substring() 的時候雖然創(chuàng)建了新的字符串,但字符串的值 value 仍然指向的是內(nèi)存中的同一個數(shù)組,如下圖所示:

如果我們僅僅是用 substring 獲取一小段字符,而原始 string字符串非常大的情況下,substring 的對象如果一直被引用。

此時 String 字符串也無法回收,從而導(dǎo)致內(nèi)存泄露。

如果有大量這種通過 substring 獲取超大字符串中一小段字符串的操作,會因為內(nèi)存泄露而導(dǎo)致內(nèi)存溢出。

JDK7、8

去掉了 offset 和 count兩個變量,減少了 String 對象占用的內(nèi)存。

substring 源碼:

  1. public String(char value[], int offset, int count) { 
  2.     this.value = Arrays.copyOfRange(value, offset, offset + count); 
  3.  
  4. public String substring(int beginIndex, int endIndex) { 
  5.     int subLen = endIndex - beginIndex; 
  6.     return new String(value, beginIndex, subLen); 

substring() 通過 new String() 返回了一個新的字符串對象,在創(chuàng)建新的對象時通過 Arrays.copyOfRange() 深度拷貝了一個新的字符數(shù)組。

如下圖所示:

String.substring 方法不再共享 char[]數(shù)組的數(shù)據(jù),解決了可能內(nèi)存泄漏的問題。

Java 9

將 char[]字段改為 byte[],新增 coder屬性。

碼哥,為什么這么改呢?

一個 char 字符占 2 個字節(jié),16 位。存儲單字節(jié)編碼內(nèi)的字符(占一個字節(jié)的字符)就顯得非常浪費。

為了節(jié)約內(nèi)存空間,于是使用了 1 個字節(jié)占 8 位的 byte 數(shù)組來存放字符串。

勤儉節(jié)約的女神,誰不愛……

新屬性 coder 的作用是:在計算字符串長度或者使用 indexOf()方法時,我們需要根據(jù)編碼類型來計算字符串長度。

coder 的值分別表示不同編碼類型:

0:表示使用 Latin-1 (單字節(jié)編碼);

1:使用UTF-16。

String 的不可變性

了解了String 的基本組成之后,發(fā)現(xiàn) String 還有一個比外在更性感的特性,她被 final關(guān)鍵字修飾,char 數(shù)組也是。

我們知道類被 final 修飾代表該類不可繼承,而 char[]被 final+private 修飾,代表了 String 對象不可被更改。

String 對象一旦創(chuàng)建成功,就不能再對它進(jìn)行改變。

final 修飾的好處

安全性

當(dāng)你在調(diào)用其他方法時,比如調(diào)用一些系統(tǒng)級操作指令之前,可能會有一系列校驗。

如果是可變類的話,可能在你校驗過后,它的內(nèi)部的值又被改變了,這樣有可能會引起嚴(yán)重的系統(tǒng)崩潰問題。

高性能緩存

String不可變之后就能保證 hash值得唯一性,使得類似 HashMap容器才能實現(xiàn)相應(yīng)的 key-value 緩存功能。

實現(xiàn)字符串常量池

由于不可變,才得以實現(xiàn)字符串常量池。

字符串常量池指的是在創(chuàng)建字符串的時候,先去「常量池」查找是否創(chuàng)建過該「字符串」;

如果有,則不會開辟新空間創(chuàng)建字符串,而是直接把常量池中該字符串的引用返回給此對象。

創(chuàng)建字符串的兩種方式:

  • String str1 = “碼哥字節(jié)”;
  • String str2 = new String(“碼哥字節(jié)”);

當(dāng)代碼中使用第一種方式創(chuàng)建字符串對象時,JVM 首先會檢查該對象是否在字符串常量池中,如果在,就返回該對象引用。

否則新的字符串將在常量池中被創(chuàng)建,并返回該引用。

這樣可以減少同一個值的字符串對象的重復(fù)創(chuàng)建,節(jié)約內(nèi)存。

第二種方式創(chuàng)建,在編譯類文件時,"碼哥字節(jié)" 字符串將會放入到常量結(jié)構(gòu)中,在類加載時,“碼哥字節(jié)" 將會在常量池中創(chuàng)建;

在調(diào)用 new 時,JVM 命令將會調(diào)用 String 的構(gòu)造函數(shù),在堆內(nèi)存中創(chuàng)建一個 String 對象,同時該對象指向「常量池」中的“碼哥字節(jié)”字符串,str 指向剛剛在堆上創(chuàng)建的 String 對象;

如下圖:

什么是對象和對象引用呀?

str 屬于方法棧的字面量,它指向堆中的 String 對象,并不是對象本。

對象在內(nèi)存中是一塊內(nèi)存地址,str 則是指向這個內(nèi)存地址的引用。

也就是說 str 并不是對象,而只是一個對象引用。

碼哥,字符串的不可變到底指的是什么呀?

  1. String str = "Java"
  2. str = "Java,yyds" 

第一次賦值 「Java」,第二次賦值「Java,yyds」,str 值確實改變了,為什么我還說 String 對象不可變呢?

這是因為 str 只是 String 對象的引用,并不是對象本身。

真正的對象依然還在內(nèi)存中,沒有被改變。

優(yōu)化實戰(zhàn)

了解了 String 的對象實現(xiàn)原理和特性,是時候要深入女神內(nèi)心,結(jié)合實際場景,如何更上一層樓優(yōu)化 String 對象的使用。

大字符串如何構(gòu)建

既然 String 對象是不可變,所以我們在頻繁拼接字符串的時候是否意味著創(chuàng)建多個對象呢?

  1. String str = "癩蛤蟆撩青蛙" + "長的丑" + "玩的花"

是不是以為先生成「癩蛤蟆撩青蛙」對象,再生成「癩蛤蟆撩青蛙長的丑」對象,最后生成「癩蛤蟆撩青蛙長得丑玩的花」對象。

實際運行中,只有一個對象生成。

這是為什么呢?

雖然代碼寫的丑陋,但是編譯器自動優(yōu)化了代碼。

再看下面例子:

  1. String str = "小青蛙"
  2.  
  3. for(int i=0; i<1000; i++) { 
  4.      str += i; 

上面的代碼編譯后,你可以看到編譯器同樣對這段代碼進(jìn)行了優(yōu)化。

Java 在進(jìn)行字符串的拼接時,偏向使用 StringBuilder,這樣可以提高程序的效率。

  1. String str = "小青蛙"
  2.  
  3. for(int i=0; i<1000; i++) { 
  4.             str = (new StringBuilder(String.valueOf(str))).append(i).toString(); 

即使如此,還是循環(huán)內(nèi)重復(fù)創(chuàng)建 StringBuilder對象。

敲黑板

所以做字符串拼接的時候,我建議你還是要顯示地使用 String Builder 來提升系統(tǒng)性能。

如果在多線程編程中,String 對象的拼接涉及到線程安全,你可以使用 StringBuffer。

運用 intern 節(jié)省內(nèi)存

直接看intern() 方法的定義與源碼:

intern() 是一個本地方法,它的定義中說的是,當(dāng)調(diào)用 intern 方法時,如果字符串常量池中已經(jīng)包含此字符串,則直接返回此字符串的引用。

否則將此字符串添加到常量池中,并返回字符串的引用。

如果不包含此字符串,先將字符串添加到常量池中,再返回此對象的引用。

什么情況下適合使用 intern() 方法?

Twitter 工程師曾分享過一個 String.intern() 的使用示例,Twitter 每次發(fā)布消息狀態(tài)的時候,都會產(chǎn)生一個地址信息,以當(dāng)時 Twitter 用戶的規(guī)模預(yù)估,服務(wù)器需要 20G 的內(nèi)存來存儲地址信息。

  1. public class Location { 
  2.     private String city; 
  3.     private String region; 
  4.     private String countryCode; 
  5.     private double longitude; 
  6.     private double latitude; 

考慮到其中有很多用戶在地址信息上是有重合的,比如,國家、省份、城市等,這時就可以將這部分信息單獨列出一個類,以減少重復(fù),代碼如下:

  1. public class SharedLocation { 
  2.  
  3.   private String city; 
  4.   private String region; 
  5.   private String countryCode; 
  6.  
  7. public class Location { 
  8.  
  9.   private SharedLocation sharedLocation; 
  10.   double longitude; 
  11.   double latitude; 

通過優(yōu)化,數(shù)據(jù)存儲大小減到了 20G 左右。

但對于內(nèi)存存儲這個數(shù)據(jù)來說,依然很大,怎么辦呢?

Twitter 工程師使用 String.intern() 使重復(fù)性非常高的地址信息存儲大小從 20G 降到幾百兆,從而優(yōu)化了 String 對象的存儲。

核心代碼如下:

  1. SharedLocation sharedLocation = new SharedLocation(); 
  2. sharedLocation.setCity(messageInfo.getCity().intern()); 
  3. sharedLocation.setCountryCode(messageInfo.getRegion().intern()); 
  4. sharedLocation.setRegion(messageInfo.getCountryCode().intern()); 

弄個簡單例子方便理解:

  1. String a =new String("abc").intern(); 
  2. String b = new String("abc").intern(); 
  3.  
  4. System.out.print(a==b); 

輸出結(jié)果:true。

在加載類的時候會在常量池中創(chuàng)建一個字符串對象,內(nèi)容是「abc」。

創(chuàng)建局部 a 變量時,調(diào)用 new Sting() 會在堆內(nèi)存中創(chuàng)建一個 String 對象,String 對象中的 char 數(shù)組將會引用常量池中字符串。

在調(diào)用 intern 方法之后,會去常量池中查找是否有等于該字符串對象的引用,有就返回引用。

創(chuàng)建 b 變量時,調(diào)用 new Sting() 會在堆內(nèi)存中創(chuàng)建一個 String 對象,String 對象中的 char 數(shù)組將會引用常量池中字符串。

在調(diào)用 intern 方法之后,會去常量池中查找是否有等于該字符串對象的引用,有就返回引用給局部變量。

而剛在堆內(nèi)存中的兩個對象,由于沒有引用指向它,將會被垃圾回收。

所以 a 和 b 引用的是同一個對象。

字符串分割有妙招

Split() 方法使用了正則表達(dá)式實現(xiàn)了其強大的分割功能,而正則表達(dá)式的性能是非常不穩(wěn)定的。

使用不恰當(dāng)會引起回溯問題,很可能導(dǎo)致 CPU 居高不下。

Java 正則表達(dá)式使用的引擎實現(xiàn)是 NFA(Non deterministic Finite Automaton,確定型有窮自動機)自動機,這種正則表達(dá)式引擎在進(jìn)行字符匹配時會發(fā)生回溯(backtracking),而一旦發(fā)生回溯,那其消耗的時間就會變得很長,有可能是幾分鐘,也有可能是幾個小時,時間長短取決于回溯的次數(shù)和復(fù)雜度。

所以我們應(yīng)該慎重使用 Split() 方法,我們可以用String.indexOf()方法代替 Split() 方法完成字符串的分割。

總結(jié)與思考

我們從 String 進(jìn)化歷程掌握了她的組成,不斷的改變成員變量節(jié)約內(nèi)存。

她的不可變性從而實現(xiàn)了字符串常量池,減少同一個字符串的重復(fù)創(chuàng)建,節(jié)約內(nèi)存。

但也是因為這個特性,我們在做長字符串拼接時,需要顯示使用 StringBuilder,以提高字符串的拼接性能。

最后,在優(yōu)化方面,我們還可以使用 intern 方法,讓變量字符串對象重復(fù)使用常量池中相同值的對象,進(jìn)而節(jié)約內(nèi)存。

通過三種不同的方式創(chuàng)建了三個對象,再依次兩兩匹配,每組被匹配的兩個對象是否相等?代碼如下:

  1. String str1 = "abc"
  2. String str2 = new String("abc"); 
  3. String str3 = str2.intern(); 
  4. assertSame(str1 == str2); 
  5. assertSame(str2 == str3); 
  6. assertSame(str1 == str3) 

本文轉(zhuǎn)載自微信公眾號「碼哥字節(jié)」

責(zé)任編輯:姜華 來源: 碼哥字節(jié)
相關(guān)推薦

2017-07-21 08:55:13

TomcatJVM容器

2020-11-03 07:48:47

當(dāng)AI入職FBI

2022-04-29 06:54:48

TS 映射類型User 類型

2012-06-20 11:05:47

性能調(diào)優(yōu)攻略

2021-03-04 08:39:21

SparkRDD調(diào)優(yōu)

2011-03-10 14:40:54

LAMPMysql

2011-05-20 15:02:01

Oracle性能調(diào)優(yōu)

2011-11-14 10:28:23

2020-11-30 11:40:35

NginxLinux性能調(diào)優(yōu)

2011-03-18 11:21:48

2022-10-25 10:20:31

線程變量原理

2024-12-04 15:49:29

2012-06-21 09:43:45

2013-02-28 10:15:14

Ubuntu性能調(diào)優(yōu)故障排查

2021-11-07 23:49:19

SQL數(shù)據(jù)庫工具

2011-03-21 09:35:38

LAMP調(diào)優(yōu)網(wǎng)絡(luò)文件

2020-11-09 07:34:49

JVM性能監(jiān)控

2013-03-20 17:18:07

Linux系統(tǒng)性能調(diào)優(yōu)

2016-03-25 09:59:38

性能調(diào)優(yōu)LinuxMySQL

2011-03-18 11:13:07

LAMP度量性能
點贊
收藏

51CTO技術(shù)棧公眾號