String對象是咱們平常工做中使用最頻繁的對象,它的性能問題也是咱們最容易忽略的。String對象做爲Java語言中最重要的數據類型,是內存中佔據空間最大的對象,高效地使用字符串,能夠提高系統的總體性能。正則表達式
今天這篇文章咱們從String對象的實現、特性以及實際使用中的優化三方面,來深刻了解String對象。編程
在Java更新的版本變化中,對String對象已經作了大量的優化,來節約內存空間,提高String對象在系統中的性能。來看看在Java版本迭代中String的優化過程;數組
在Java6以及之前的版本中,String對象是對char數組進行了封裝實現的對象,主要有四個成員變量:char數組、偏移量offset、字符數量count、哈希值hash。緩存
在Java7和8版本中,Java對String類作了改變,再也不有offset和count兩個變量,這樣能夠稍微減小String對象佔用的內存。同時,String.substring()再也不共享char[],從而解決了使用該方法可能致使的內存泄露問題。安全
從Java9版本開始,char[]改爲了byte[],又維護了一個新的屬性coder,它是一個編碼格式的標識。多線程
爲何從char[]改變成byte[],咱們都知道一個char字符佔用16位,2個字節,這種狀況在存儲單字節編碼內的字符就有點浪費。Java9中String類爲了更加節約內存空間,選擇了佔用8位,1字節的byte數組來存放字符串。app
新屬性coder的做用是在計算字符串長度或者使用indexOf()函數時,咱們須要根據這個字段,判斷如何計算字符串長度。coder屬性默認有0和1兩個值,0表明Latin-1(單字節編碼),1表明UTF-16。若是String判斷字符串只包含了Latin-1,則coder屬性值爲0,反之則爲1.ide
咱們發如今String對象實現中, 不只實現代碼的String類被final關鍵字修飾,並且遍歷charp[]也被final修飾。類被final修飾表明String類不能被繼承,而charp[]被private和final修飾,表明了String對象不可被更改。Java實現的這個特性叫作String對象的不可變性,即String對象一旦被建立成功就不能進行修改了。函數
String對象不可變性有哪些好處?性能
保證了String對象的安全性。若是 String 對象是可變的,那麼 String 對象將可能被惡意修改。
保證了hash屬性值不會頻繁變動,確保了惟一性,使得相似 HashMap 容器才能實現相應的 key-value 緩存功能。
能夠實現字符串常量池。Java中有兩種建立字符串對象的方式,一種是經過字符串常量的方式建立,另一種是字符串變量經過new的形式建立。
當咱們使用字符串常量建立字符串對象時,JVM會先檢查該對象是在字符串常量池中,若是在就返回該對象的引用,不然新建立一個字符串對象保存到字符串常量池,並使用這個引用。這種方式能夠減小同一個值的字符串對象的重複建立,節約了內存空間。
當咱們使用new的形式建立,好比String str = new String(「abc」),在編譯類文件的時候,「abc」常量字符串將會放入常量結構中,在類加載時,「abc」將會放到常量池中建立,在調用new時,JVM命令將會調用String的構造函數,同時引用常量池中的「abc"字符串,在堆內存中建立一個String對象,最後 str引用String對象。
上邊咱們瞭解了String對象實現原理和特性,下邊將結合實際場景,看看String對象在咱們實際使用中有哪些須要注意的地方。
在編程過程當中,字符串的拼接很常見。前邊咱們也說了String對象是不可變的,若是咱們使用String對象相加,拼接咱們想要的字符串,就會產生多個對象。例如以下代碼:
String str = "ab" + "cd" + "ef";
分析可知,首先會生成ab對象,再生成abcd對象,最後生成abcdef對象,從理論上講,這樣作的效率很低。可是實際運行中,咱們發現只有一個對象生成,這是爲何?咱們再來看看編譯後的代碼,你會發現上邊的代碼編譯器自動作了優化,以下:
String str = "abcdef";
上面介紹的是字符串常量的累加,再來看看字符串變量的累加:
String str = "abcdef";for (int i = 0; i<1000;i++) { str = str + i;}
編譯後,咱們能夠看到編譯器一樣對這段代碼進行了優化,Java在進行字符串拼接時,偏向於使用StringBuilder,這樣能夠提升程序的效率。
String str = "abcdef";for(int i=0; i<1000; i++) { str = (new StringBuilder(String.valueOf(str))).append(i).toString();}
咱們能夠看到即便使用‘’+」號做爲字符串拼接,也同樣被編譯器優化成StringBuilder的方式,可是,仔細一看,你會發現編譯器優化後的代碼,每次循環的時候都會生成一個新的StringBuilder對象,一樣會下降系統的性能。
咱們平時作字符串拼接的時候,建議顯示地使用StringBuilder來提高系統性能,若是是多線程編程中,String對象的拼接涉及到線程安全,可使用StringBuffer。因爲StringBuffer是線程安全的,涉及到鎖競爭,因此從性能上說,要比StringBuilder差一些。
在每次賦值的時候使用 String 的 intern 方法,若是常量池中有相同值,就會重複使用該對象,返回對象引用,爲了更好的理解,咱們舉一個簡單的例子來看一下:
String a =new String("abc").intern();String b = new String("abc").intern(); if(a==b) { System.out.print("a==b");}
輸出結果,a==b
,分析一下;
建立 a 變量時,調用 new Sting() 會在堆內存中建立一個 String 對象,String 對象中的 char 數組將會引用常量池中字符串。在調用 intern 方法以後,會去常量池中查找是否有等於該字符串對象的引用,有就返回引用。
建立 b 變量時,調用 new Sting() 會在堆內存中建立一個 String 對象,String 對象中的 char 數組將會引用常量池中字符串。在調用 intern 方法以後,會去常量池中查找是否有等於該字符串對象的引用,有就返回引用。
而在堆內存中的兩個對象,因爲沒有引用指向它,將會被垃圾回收。因此 a 和 b 引用的是同一個對象。
若是在運行時,建立字符串對象,將會直接在堆內存中建立,不會在常量池中建立。因此動態建立的字符串對象,調用 intern 方法,在 JDK1.6 版本中會去常量池中建立運行時常量以及返回字符串引用,在 JDK1.7 版本以後,會將堆中的字符串常量的引用放入到常量池中,當其它堆中的字符串對象經過 intern 方法獲取字符串對象引用時,則會去常量池中判斷是否有相同值的字符串的引用,此時有,則返回該常量池中字符串引用,跟以前的字符串指向同一地址的字符串對象。
用一張圖來總結String字符串的建立和分配內存地址的狀況:
使用 intern 方法須要注意的一點是,必定要結合實際場景。由於常量池的實現是相似於一個 HashTable 的實現方式,HashTable 存儲的數據越大,遍歷的時間複雜度就會增長。若是數據過大,會增長整個字符串常量池的負擔。
分割字符串咱們經常使用的方法就是Split()方法,Split()方法使用了正則表達式實現了其強大的分割能力,可是正則表達式的性能是很不穩定的,使用不當就會引發回溯問題,頗有可能致使CPU居高不下。
在平常使用的時候,能夠用String.indexOf()方法代替Split()方法完成對字符串的分割,若是沒法知足須要,在使用Split()方法的時候對回溯問題要加以重視。
以一個小問題結束本次分享,下邊每組匹配的兩個對象是否相等?
String str1= "abc";String str2= new String("abc");String str3= str2.intern();assertSame(str1==str2);assertSame(str2==str3);assertSame(str1==str3)