摘要:而用關鍵字調用構造器,總是會創建一個新的對象,無論內容是否相同。中對象的哈希碼被頻繁地使用比如在等容器中。字符串不變性保證了碼的唯一性因此可以放心地進行緩存。對于所有包含方式新建對象包括的連接表達式,它所產生的新對象都不會被加入字符串池中。
前言
前陣子和同事在吃飯時聊起Java的String,覺得自己之前的筆記寫的略顯零散。故此又重新整理了一下。
String在Java中算是一個有意思的類型,是final類型,因此不可以繼承這個類、不能修改這個類。
兩個小問題我們先來看一段代碼:
String s = "Hello"; s = s + " world!";
試問:這兩行代碼執行后,原始的 String 對象中的內容到底變了沒有?
答案是沒有。因為 String 被設計成不可變(immutable)類,所以它的所有對象都是不可變對象。在 這段代碼中,s 原先指向一個 String 對象,內容是 "Hello",然后我們對 s 進行了+操作。這時,s 不指向原來那個對象了, 而指向了另一個 String 對象,內容為"Hello world!",原來那個對象還存在于內存之中,只 是 s 這個引用變量不再指向它了。
通過上面的說明,我們很容易導出另一個結論,如果經常對字符串進行各種各樣的修改,或 者說,不可預見的修改,那么使用 String 來代表字符串的話會引起很大的內存開銷。因為 String 對象建立之后不能再改變,所以對于每一個不同的字符串,都需要一個 String 對象來 表示。這時,應該考慮使用 StringBuffer類,它允許修改,而不是每個不同的字符串都要生 成一個新的對象。并且,這兩種類的對象轉換十分容易。
同時,我們還可以知道,如果要使用內容相同的字符串,不必每次都 new 一個 String。例 如我們要在構造器中對一個名叫 s 的 String 引用變量進行初始化,把它設置為初始值,應當這樣做:
public class Demo { private String s; ... public Demo { s = "Initial Value"; } ... //而非 s = new String("Initial Value"); }
前者每次都會調用構造器,生成新對象,性能低下且內存開銷大,并且沒有意義,因為 String 對象不可改變,所以對于內容相同的字符串,只要一個 String 對象來表示就可以了。也就 說,多次調用上面的構造器創建多個對象,他們的 String 類型屬性 s 都指向同一個對象。 上面的結論還基于這樣一個事實:對于字符串常量,如果內容相同,Java 認為它們代表同 一個 String 對象。而用關鍵字 new 調用構造器,總是會創建一個新的對象,無論內容是否 相同。
再請大家看一段代碼:
String s = new String("xyz");
問題:創建了幾個 String Object?二者之間有什么區別?
一個或兩個 。
”xyz”對應一個對象,這個對象放在字符串常量池,常量”xyz”不管出現多少遍,都是緩沖區中的那一個。New String 每寫一遍,就創建一個新的對象在堆上。
如果以前就用過’xyz’,這句代表就不會 創建”xyz”自己了,直接從字符串常量池拿。
常量池在Java中,其實有很多常量池相關的概念:
常量池表(constant_pool table)Class文件中存儲所有常量(包括字符串)的table
這是Class文件中的內容,還不是運行時的內容,不要理解它是個池子,其實就是Class文件中的字節碼指令
運行時常量池(Runtime Constant Pool)JVM內存中方法區的一部分,這是運行時的內容
這部分內容(絕大部分)是隨著JVM運行時候,從常量池轉化而來,每個Class對應一個運行時常量池
前面說的絕大部分是因為:除了 Class中常量池內容,還可能包括動態生成并加入這里的內容
字符串常量池(String Pool)這部分也在方法區中,但與Runtime Constant Pool不是一個概念,String Pool是JVM實例全局共享的,全局只有一個
JVM規范要求進入這里的String實例叫“被駐留的interned string”,各個JVM可以有不同的實現,HotSpot是設置了一個哈希表StringTable來引用堆中的字符串實例,被引用就是被駐留。
類似這種常量池的思想即涉及到了一個設計模式——享元模式。顧名思義,共享元素。
也就是說:一個系統中如果有多處用到了相同的一個元素,那么我們應該只存儲一份此元素,而讓所有地方都引用這一個元素
不可變的String那么為什么要不可變呢?
主要是為了安全與效率。
安全String被許多的Java類庫用來當做參數。例:
URL、IP
文件路徑path
反射機制所需要的String參數
等等...
假若String不是固定不變的,將會引起各種安全隱患。
效率在前面提到過常量池的享元模式。這樣在拷貝或者創建內容相同對象時,就不必復制對象本身,而是只要引用它即可。這樣的開銷比起copy object是天差地別的。另外,也就只有不可變對象才能使用常量池,因為可以保證引用同一常量值的多個變量不產生相互影響。
同樣也是由于String對象的不可變特性,所以String對象可以自身緩存HashCode。Java中String對象的哈希碼被頻繁地使用, 比如在hashMap 等容器中。字符串不變性保證了hash碼的唯一性,因此可以放心地進行緩存。這也是一種性能優化手段,意味著不必每次都去計算新的哈希碼:
public final class String implements java.io.Serializable, Comparable其他 String 和 StringBuffer, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0
JAVA 平臺提供了兩個類:String 和 StringBuffer,它們可以儲存和操作字符串,即包含多個字符的字符數據。這個 String 類提供了數值不可改變的字符串。而這個 StringBuffer 類提供 的字符串進行修改。當你知道字符數據要改變的時候你就可以使用 StringBuffer。典型地, 你可以使用 StringBuffers 來動態構造字符數據。另外,String 實現了 equals 方法,new String(“abc”).equals(newString(“abc”)的結果為true,而StringBuffer沒有實現equals方法, 所以,new StringBuffer(“abc”).equals(newStringBuffer(“abc”)的結果為 false。
接著要舉一個具體的例子來說明,我們要把1到100的所有數字拼起來,組成一個串。
StringBuffer sbf = new StringBuffer(); for(int i=0;i<100;i++){ sbf.append(i); }
上面的代碼效率很高,因為只創建了一個 StringBuffer 對象,而下面的代碼效率很低,因為 創建了101個對象。
String str = new String(); for(int i=0;i<100;i++) { str = str + i; }
在講兩者區別時,應把循環的次數搞成10000,然后用 endTime-beginTime 來比較兩者執 行的時間差異。
String 覆蓋了 equals 方法和 hashCode 方法,而 StringBuffer沒有覆蓋 equals 方法和 hashCode 方法,所以,將 StringBuffer對象存儲進 Java集合類中時會出現問題。
StringBuilder與 StringBufferStringBuilder不是線程安全的,但是單線程中中的性能比StringBuffer高。
Demo Code String對象創建方式String str1 = "abcd"; String str2 = new String("abcd"); System.out.println(str1==str2);//false
這兩種不同的創建方法是有差別的:
第一種方式是在常量池中拿對象
第二種方式是直接在堆內存空間創建一個新的對象。只要使用new方法,便會創建新的對象
連接表達式 +只有使用引號包含文本的方式創建的String對象之間使用“+”連接產生的新對象才會被加入字符串池中。
對于所有包含new方式新建對象(包括null)的“+”連接表達式,它所產生的新對象都不會被加入字符串池中。
String str1 = "str"; String str2 = "ing"; String str3 = "str" + "ing"; String str4 = str1 + str2; System.out.println(str3 == str4);//false String str5 = "string"; System.out.println(str3 == str5);//true連接表達式demo1
public static final String str1 = "ab"; public static final String str2 = "cd"; public static void main(String[] args) { String s = str1 + str2; // 將兩個常量用+連接對s進行初始化 String t = "abcd"; if (s == t) { System.out.println("s等于t,它們是同一個對象"); } else { System.out.println("s不等于t,它們不是同一個對象"); } }
s等于t,它們是同一個對象
A和B都是常量,值是固定的,因此s的值也是固定的,它在類被編譯時就已經確定了。也就是說:String s=A+B; 等同于:String s="ab"+"cd";
連接表達式demo2public static final String str1; public static final String str2; static { str1 = "ab"; str2 = "cd"; } public static void main(String[] args) { // 將兩個常量用+連接對s進行初始化 String s = str1 + str2; String t = "abcd"; if (s == t) { System.out.println("s等于t,它們是同一個對象"); } else { System.out.println("s不等于t,它們不是同一個對象"); } }
s不等于t,它們不是同一個對象
A和B雖然被定義為常量,但是它們都沒有馬上被賦值。在運算出s的值之前,他們何時被賦值,以及被賦予什么樣的值,都是個變數。因此A和B在被賦值之前,性質類似于一個變量。那么s就不能在編譯期被確定,而只能在運行時被創建了。
intern方法運行時常量池相對于Class文件常量池的另外一個重要特征是具備動態性,Java語言并不要求常量一定只有編譯期才能產生,也就是并非預置入Class文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用比較多的就是String類的intern()方法。
String的intern()方法會查找在常量池中是否存在一份equal相等的字符串,如果有則返回該字符串的引用,如果沒有則添加自己的字符串進入常量池。
public static void main(String[] args) { String s1 = new String("計算機"); String s2 = s1.intern(); String s3 = "計算機"; System.out.println("s1 == s2? " + (s1 == s2)); System.out.println("s3 == s2? " + (s3 == s2)); } //s1 == s2? false //s3 == s2? true一個較為豐富的demo
public class Test { public static void main(String[] args) { String hello = "Hello", lo = "lo"; System.out.println((hello == "Hello") + " "); System.out.println((Other.hello == hello) + " "); System.out.println((other.Other.hello == hello) + " "); System.out.println((hello == ("Hel" + "lo")) + " "); System.out.println((hello == ("Hel" + lo)) + " "); System.out.println(hello == ("Hel" + lo).intern()); } }
class Other { static String hello = "Hello"; }
package other; public class Other { public static String hello = "Hello"; } //true true true true false true
在同包同類下,引用自同一String對象
在同包不同類下,引用自同一String對象
在不同包不同類下,依然引用自同一String對象
在編譯成.class時能夠識別為同一字符串的,自動優化成常量,引用自同一String對象
在運行時創建的字符串具有獨立的內存地址,所以不引用自同一String對象
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/67997.html
摘要:常量接口是對接口的一種不良使用。如果這些常量最好被看作是枚舉類型成員,那就應該用枚舉類型來導出。因為客戶端既不能創建枚舉類型的實例,也不能對它進行擴展,因此很可能沒有實例,而只有聲明過的枚舉常量。換句話說,枚舉類型是實例受控的。 問題 我們偶爾能在項目中看到如下風格的代碼: public class ResponseCode { public static final int ...
摘要:一前言本文章將以報表下載為例,給大家介紹三種文件下載的方式。通過二進制數據流的方式下載這種方式是我目前采用的方式,用于處理報表下載。缺點對于數據量不大的文件,這種方式是可行的。 一、前言 本文章將以excel報表下載為例,給大家介紹三種文件下載的方式。 原文地址:簡談文件下載的三種方式 | Rychou 二、正文 1. 通過服務器文件地址下載 這是最常見的文件下載方式,大多數網站的音頻...
摘要:一前言本文章將以報表下載為例,給大家介紹三種文件下載的方式。通過二進制數據流的方式下載這種方式是我目前采用的方式,用于處理報表下載。缺點對于數據量不大的文件,這種方式是可行的。 一、前言 本文章將以excel報表下載為例,給大家介紹三種文件下載的方式。 原文地址:簡談文件下載的三種方式 | Rychou 二、正文 1. 通過服務器文件地址下載 這是最常見的文件下載方式,大多數網站的音頻...
摘要:所以經常看到的說閉包就是綁定了上下文環境的函數。我更偏向于閉包是一個函數和聲明該函數的詞法環境的組合。里面的閉包先上一個閉包該例子的解釋上面的代碼,在函數里面定義的函數和這個函數聲明的詞法環境就形成了一個閉包。 閉包是什么 第一種說法:閉包創建一個詞法作用域,這個作用域里面的變量被引用之后可以在這個詞法作用域外面被自由訪問,是一個函數和聲明該函數的詞法環境的組合 第二種說法:閉包就是...
閱讀 1841·2021-08-19 11:12
閱讀 1418·2021-07-25 21:37
閱讀 980·2019-08-30 14:07
閱讀 1260·2019-08-30 13:12
閱讀 645·2019-08-30 11:00
閱讀 3523·2019-08-29 16:28
閱讀 982·2019-08-29 15:33
閱讀 2960·2019-08-26 13:40