成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java中的JVM字符串性能優(yōu)化

開發(fā) 開發(fā)工具
String 對象是我們使用很頻繁的一個對象類型,但它的性能問題卻是很容易被忽略的。String 對象作為 Java 語言中重要的數(shù)據類型,是內存中占據空間較大的一個對象。高效地使用字符串,可以提升系統(tǒng)的整體性能。

 一、引言

String 對象是我們使用很頻繁的一個對象類型,但它的性能問題卻是很容易被忽略的。String 對象作為 Java 語言中重要的數(shù)據類型,是內存中占據空間較大的一個對象。高效地使用字符串,可以提升系統(tǒng)的整體性能。

[[285816]]

二、String 對象的實現(xiàn)

在 Java 語言中,Sun 公司的工程師們對 String 對象做了大量的優(yōu)化,來節(jié)約內存空間,提升 String 對象在系統(tǒng)中的性能。

1. 在 Java6 以及之前的版本中,String 對象是對 char 數(shù)組進行了封裝實現(xiàn)的對象,主要有四個成員變量:char 數(shù)組、偏移量 offset、字符數(shù)量 count、哈希值 hash。String 對象是通過 offset 和 count 兩個屬性來定位 char[] 數(shù)組,獲取字符串。這么做可以高效、快速地共享數(shù)組對象,同時節(jié)省內存空間,但這種方式很有可能會導致內存泄漏。

2. 從 Java7 版本開始到 Java8 版本,Java 對 String 類做了一些改變。String 類中不再有 offset 和 count 兩個變量了。這樣的好處是 String 對象占用的內存稍微少了些,同時,String.substring 方法也不再共享 char[],從而解決了使用該方法可能導致的內存泄漏問題。

3. 從 Java9 版本開始,工程師將 char[] 字段改為了 byte[] 字段,又維護了一個新的屬性 coder,它是一個編碼格式的標識。

工程師為什么這樣修改呢?

我們知道一個 char 字符占 16 位,2 個字節(jié)。這個情況下,存儲單字節(jié)編碼內的字符(占一個字節(jié)的字符)就顯得非常浪費。JDK1.9 的 String 類為了節(jié)約內存空間,于是使用了占 8 位,1 個字節(jié)的 byte 數(shù)組來存放字符串。

而新屬性 coder 的作用是,在計算字符串長度或者使用 indexOf()函數(shù)時,我們需要根據這個字段,判斷如何計算字符串長度。coder 屬性默認有 0 和 1 兩個值,0 代表 Latin-1(單字節(jié)編碼),1 代表 UTF-16。如果 String 判斷字符串只包含了 Latin-1,則 coder 屬性值為 0,反之則為 1。

三、String 對象的不可變性起因

了解了 String 對象的實現(xiàn)后,你有沒有發(fā)現(xiàn)在實現(xiàn)代碼中 String 類被 final 關鍵字修飾了,而且變量 char 數(shù)組也被 final 修飾了。

我們知道類被 final 修飾代表該類不可繼承,而 char[] 被 final+private 修飾,代表了 String 對象不可被更改。Java 實現(xiàn)的這個特性叫作 String 對象的不可變性,即 String 對象一旦創(chuàng)建成功,就不能再對它進行改變。

四、String 對象的不可變性好處

1.保證 String 對象的安全性。假設 String 對象是可變的,那么 String 對象將可能被惡意修改。

2.保證 hash 屬性值不會頻繁變更,確保了唯一性,使得類似 HashMap 容器才能實現(xiàn)相應的 key-value 緩存功能。

3.可以實現(xiàn)字符串常量池。在 Java 中,通常有兩種創(chuàng)建字符串對象的方式,一種是通過字符串常量的方式創(chuàng)建,如 String str=“abc”;另一種是字符串變量通過 new 形式的創(chuàng)建,如 String str = new String(“abc”)。

當代碼中使用第一種方式創(chuàng)建字符串對象時,JVM 首先會檢查該對象是否在字符串常量池中,如果在,就返回該對象引用,否則新的字符串將在常量池中被創(chuàng)建。這種方式可以減少同一個值的字符串對象的重復創(chuàng)建,節(jié)約內存。

String str = new String(“abc”) 這種方式,首先在編譯類文件時,“abc”常量字符串將會放入到常量結構中,在類加載時,“abc”將會在常量池中創(chuàng)建;其次,在調用 new 時,JVM 命令將會調用 String 的構造函數(shù),同時引用常量池中的“abc” 字符串,在堆內存中創(chuàng)建一個 String 對象;最后,str 將引用 String 對象。

五、String 對象的優(yōu)化

1. 如何構建超大字符串?

編程過程中,字符串的拼接很常見。前面我講過 String 對象是不可變的,如果我們使用 String 對象相加,拼接我們想要的字符串,是不是就會產生多個對象呢?例如以下代碼:

 

  1. String str= "ab" + "cd" + "ef"

分析代碼可知:首先會生成 ab 對象,再生成 abcd 對象,最后生成 abcdef 對象,從理論上來說,這段代碼是低效的。

但實際運行中,我們發(fā)現(xiàn)只有一個對象生成,這是為什么呢?難道我們的理論判斷錯了?我們再來看編譯后的代碼,你會發(fā)現(xiàn)編譯器自動優(yōu)化了這行代碼,如下:

 

  1. String str= "abcdef"

上面介紹的是字符串常量的累計,再來看看字符串變量的累計又是怎樣的呢?

 

  1. String str = "abcdef"
  2.  
  3. for(int i=0; i<1000; i++) { 
  4.  
  5. str = str + i; 
  6.  

 

上面的代碼編譯后,你可以看到編譯器同樣對這段代碼進行了優(yōu)化。不難發(fā)現(xiàn),Java 在進行字符串的拼接時,偏向使用 StringBuilder,這樣可以提高程序的效率。

 

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

 

綜上已知:即使使用 + 號作為字符串的拼接,也一樣可以被編譯器優(yōu)化成 StringBuilder 的方式。但再細致些,你會發(fā)現(xiàn)在編譯器優(yōu)化的代碼中,每次循環(huán)都會生成一個新的 StringBuilder 實例,同樣也會降低系統(tǒng)的性能。

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

如果在多線程編程中,String 對象的拼接涉及到線程安全,你可以使用 StringBuffer。但是要注意,由于 StringBuffer 是線程安全的,涉及到鎖競爭,所以從性能上來說,要比 StringBuilder 差一些。

2. 如何使用 String.intern 節(jié)省內存?

講完了構建字符串,我們再來討論下 String 對象的存儲問題。先看一個案例。

Twitter 每次發(fā)布消息狀態(tài)的時候,都會產生一個地址信息,以當時 Twitter 用戶的規(guī)模預估,服務器需要 32G 的內存來存儲地址信息。

 

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

 

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

 

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

 

通過優(yōu)化,數(shù)據存儲大小減到了 20G 左右。但對于內存存儲這個數(shù)據來說,依然很大,怎么辦呢?

這個案例來自一位 Twitter 工程師在 QCon 全球軟件開發(fā)大會上的演講,他們想到的解決方法,就是使用 String.intern 來節(jié)省內存空間,從而優(yōu)化 String 對象的存儲。

具體做法就是,在每次賦值的時候使用 String 的 intern 方法,如果常量池中有相同值,就會重復使用該對象,返回對象引用,這樣一開始的對象就可以被回收掉。這種方式可以使重復性非常高的地址信息存儲大小從 20G 降到幾百兆。

 

  1. SharedLocation sharedLocation = new SharedLocation(); 
  2.  
  3. sharedLocation.setCity(messageInfo.getCity().intern()); sharedLocation.setCountryCode(messageInfo.getRegion().intern()); 
  4.  
  5. sharedLocation.setRegion(messageInfo.getCountryCode().intern()); 
  6.  
  7. Location location = new Location(); 
  8.  
  9. location.set(sharedLocation); 
  10.  
  11. location.set(messageInfo.getLongitude()); 
  12.  
  13. location.set(messageInfo.getLatitude()); 

 

為了更好地理解,我們再來通過一個簡單的例子,回顧下其中的原理:

 

  1. String a =new String("abc").intern(); 
  2.  
  3. String b = new String("abc").intern(); 
  4.  
  5. if(a==b) { 
  6.  
  7. System.out.print("a==b"); 
  8.  

 

輸出結果:

 

  1. a==b 

在字符串常量中,默認會將對象放入常量池;在字符串變量中,對象是會創(chuàng)建在堆內存中,同時也會在常量池中創(chuàng)建一個字符串對象,復制到堆內存對象中,并返回堆內存對象引用。

如果調用 intern 方法,會去查看字符串常量池中是否有等于該對象的字符串的引用,如果沒有,在 JDK1.6 版本中會復制堆中的字符串到常量池中,并返回該字符串引用,堆內存中原有的字符串由于沒有引用指向它,將會通過垃圾回收器回收。

在 JDK1.7 版本以后,由于常量池已經合并到了堆中,所以不會再復制具體字符串了,只是會把首次遇到的字符串的引用添加到常量池中;如果有,就返回常量池中的字符串引用。

了解了原理,我們再一起看下上邊的例子。

在一開始字符串“abc”會在加載類時,在常量池中創(chuàng)建一個字符串對象。

創(chuàng)建 a 變量時,調用 new String() 會在堆內存中創(chuàng)建一個 String 對象,String 對象中的 char 數(shù)組將會引用常量池中字符串。在調用 intern 方法之后,會去常量池中查找是否有等于該字符串對象的引用,有就返回引用。

創(chuàng)建 b 變量時,調用 new String() 會在堆內存中創(chuàng)建一個 String 對象,String 對象中的 char 數(shù)組將會引用常量池中字符串。在調用 intern 方法之后,會去常量池中查找是否有等于該字符串對象的引用,有就返回引用。

而在堆內存中的兩個對象,由于沒有引用指向它,將會被垃圾回收。所以 a 和 b 引用的是同一個對象。

如果在運行時,創(chuàng)建字符串對象,將會直接在堆內存中創(chuàng)建,不會在常量池中創(chuàng)建。所以動態(tài)創(chuàng)建的字符串對象,調用 intern 方法,在 JDK1.6 版本中會去常量池中創(chuàng)建運行時常量以及返回字符串引用,在 JDK1.7 版本之后,會將堆中的字符串常量的引用放入到常量池中,當其他堆中的字符串對象通過 intern 方法獲取字符串對象引用時,則會去常量池中判斷是否有相同值的字符串的引用,此時有,則返回該常量池中字符串引用,跟之前的字符串指向同一地址的字符串對象。

以一張圖來總結 String 字符串的創(chuàng)建分配內存地址情況:

使用 intern 方法需要注意的一點是,一定要結合實際場景。因為常量池的實現(xiàn)是類似于一個 HashTable 的實現(xiàn)方式,HashTable 存儲的數(shù)據越大,遍歷的時間復雜度就會增加。如果數(shù)據過大,會增加整個字符串常量池的負擔。

3. 如何使用字符串的分割方法?

Split() 方法使用了正則表達式實現(xiàn)了其強大的分割功能,而正則表達式的性能是非常不穩(wěn)定的,使用不恰當會引起回溯問題,很可能導致 CPU 居高不下。

所以我們應該慎重使用 Split() 方法,我們可以用 String.indexOf() 方法代替 Split() 方法完成字符串的分割。如果實在無法滿足需求,你就在使用 Split() 方法時,對回溯問題加以重視就可以了。

六、總結

我們認識到做好 String 字符串性能優(yōu)化,可以提高系統(tǒng)的整體性能。在這個理論基礎上,Java 版本在迭代中通過不斷地更改成員變量,節(jié)約內存空間,對 String 對象進行優(yōu)化。

我們還特別提到了 String 對象的不可變性,正是這個特性實現(xiàn)了字符串常量池,通過減少同一個值的字符串對象的重復創(chuàng)建,進一步節(jié)約內存。

但也是因為這個特性,我們在做長字符串拼接時,需要顯示使用 StringBuilder,以提高字符串的拼接性能。最后,在優(yōu)化方面,我們還可以使用 intern 方法,讓變量字符串對象重復使用常量池中相同值的對象,進而節(jié)約內存。

最后再分享一個個人觀點。那就是千里之堤,潰于蟻穴。日常編程中,我們往往可能就是對一個小小的字符串了解不夠深入,使用不夠恰當,從而引發(fā)線上事故。

比如,在我之前的工作經歷中,就曾因為使用正則表達式對字符串進行匹配,導致并發(fā)瓶頸,這里也可以將其歸納為字符串使用的性能問題。

【本文是51CTO專欄機構“AiChinaTech”的原創(chuàng)文章,微信公眾號( id: tech-AI)”】

 

戳這里,看該作者更多好文

 

責任編輯:華軒 來源: 51CTO
相關推薦

2024-01-03 08:20:05

Java字符串性能

2013-06-24 15:16:29

Java字符串拼接

2025-03-27 03:20:00

C#開發(fā)字符串

2022-05-18 10:56:58

Java字符串編碼

2013-10-16 10:45:29

JVMJava

2009-09-02 16:21:20

C#字符串

2011-06-08 15:45:41

字符串JAVA

2025-01-03 08:31:43

2009-08-21 15:06:09

C#連接字符串

2024-03-05 18:29:54

JavaString字符串

2010-06-28 15:18:51

SQL Server

2010-03-18 08:59:29

JVM字符串JVM常量池

2011-06-22 10:45:19

JAVA

2021-03-08 08:23:24

Java字符串截取

2015-08-14 09:37:44

Java字符串基本運算

2010-09-06 17:30:46

SQL函數(shù)

2021-03-11 18:44:39

字符串SQL表達式

2021-09-10 08:18:31

Go語言字符串

2009-06-23 14:13:00

Java字符串

2009-02-24 15:39:27

字符串比較函數(shù)函數(shù)
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91精品国产91久久久久久最新 | 99国产精品99久久久久久 | 久久精品国产亚洲一区二区 | 欧美在线一区二区三区 | 亚洲精品一区二区三区免 | 国产欧美日韩在线一区 | 97国产精品视频人人做人人爱 | 国产精品片aa在线观看 | 99精品在线观看 | 日韩欧美在线观看 | 国产精品久久久久久久久久久久 | 国产一区二区三区久久久久久久久 | 91在线免费观看网站 | 欧美日韩国产精品一区 | 久久在线精品 | 久久丝袜| 日韩视频中文字幕 | 亚洲精品欧美 | 久久久久精| 亚洲日韩中文字幕一区 | 视频在线亚洲 | 国产欧美一区二区在线观看 | 精品欧美久久 | 亚洲精品一区二区三区蜜桃久 | 久久久精品网 | 欧美日本韩国一区二区三区 | 欧美一区二区在线看 | 亚洲一区二区高清 | 交专区videossex农村 | 精品产国自在拍 | 在线观看h视频 | 精品久久久久久久 | 亚洲精品久久视频 | 一区二区三区影院 | 精品日韩一区二区 | 欧美综合在线观看 | 国产精品中文字幕在线 | 欧美精品二区 | av手机在线播放 | 亚洲婷婷六月天 | 日韩欧美二区 |