摘要:種基本類型的常量池都是系統協調的,類型的常量池比較特殊。在中,字符串常量池已經從區移到正常的區域。此時引用對象內容是,但此時常量池中是沒有對象的。這樣,首先執行聲明的時候常量池中是不存在對象的,執行完畢后,對象是聲明產生的新對象。
概覽 1. 類聲明
String 被聲明為 final,因此它不可被繼承。
在 Java 8 及之前,內部使用 char 數組存儲數據。
public final class String implements java.io.Serializable, Comparable, CharSequence { /** The value is used for character storage. */ private final char value[]; }
在 Java 9 及之后,String 類的實現改用 byte 數組存儲字符串,同時使用 coder來標識使用了哪種字符集編碼。
public final class String implements java.io.Serializable, Comparable2. 構造函數, CharSequence { /** The value is used for character storage. */ private final byte[] value; /** The identifier of the encoding used to encode the bytes in {@code value}. */ private final byte coder; }
空參構造
/** * final聲明的 value數組不能修改它的引用,所以在構造函數中一定要初始化value屬性 */ public String() { this.value = "".value; }
用一個String來構造
/** * 除非你明確需要 這個original字符串的 副本 */ public String(String original) { this.value = original.value; this.hash = original.hash; }
用char數組來構造
public String(char value[], int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count <= 0) { if (count < 0) { throw new StringIndexOutOfBoundsException(count); } if (offset <= value.length) { this.value = "".value; return; } } // Note: offset or count might be near -1>>>1. if (offset > value.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } this.value = Arrays.copyOfRange(value, offset, offset+count); }
用byte[]來構造
/** * 構造一個由byte[]生產的字符串,使用系統默認字符集編碼 * 新數組的長度 不一定等于 數組的length * 如果默認字符集編碼不可用時,此構造器無效。 */ public String(byte bytes[], int offset, int length) { checkBounds(bytes, offset, length); this.value = StringCoding.decode(bytes, offset, length); }
用 Unicode編碼的int[]來構造
/** * 使用 Unicode編碼的int數組 初始化字符串 * 入參數組修改不影響新創建的String * @since 1.5 */ public String(int[] codePoints, int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count <= 0) { if (count < 0) { throw new StringIndexOutOfBoundsException(count); } //count = 0 if (offset <= codePoints.length) { this.value = "".value; return; } } // Note: offset or count might be near -1>>>1. if (offset > codePoints.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } final int end = offset + count; // Pass 1: Compute precise size of char[] int n = count; for (int i = offset; i < end; i++) { int c = codePoints[i]; // 從 U+0000 至 U+FFFF 之間的字符集有時候被稱為基本多語言面 // 可以使用單個char來表示這樣的代碼點 if (Character.isBmpCodePoint(c)) continue; // 確認c 是不是 else if (Character.isValidCodePoint(c)) n++; else throw new IllegalArgumentException(Integer.toString(c)); } // Pass 2: Allocate and fill in char[] // 得到可以轉成有效字符的 個數 final char[] v = new char[n]; for (int i = offset, j = 0; i < end; i++, j++) { int c = codePoints[i]; if (Character.isBmpCodePoint(c)) v[j] = (char)c; else Character.toSurrogates(c, v, j++); } this.value = v; } }
用變長字符串StringBuffer,StringBuilder來構造
public String(StringBuffer buffer) { synchronized(buffer) { this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); } } public String(StringBuilder builder) { this.value = Arrays.copyOf(builder.getValue(), builder.length()); }3. 常用api
方法列表:
boolean isEmpty() //當且僅當 length() 為 0 時返回 true int length() //返回此字符串的長度 boolean contains(CharSequence s) //當且僅當此字符串包含指定的 char 值序列時,返回 true char charAt(int index) //返回指定索引處的 char 值 String concat(String str) //將指定字符串連接到此字符串的結尾 int indexOf(int ch) //返回指定字符在此字符串中第一次出現處的索引 int lastIndexOf(int ch) //返回指定字符在此字符串中最后一次出現處的索引 String substring(int beginIndex, int endIndex) //返回一個新字符串,它是此字符串的一個子字符串 CharSequence subSequence(int beginIndex, int endIndex) //返回一個新的字符序列,它是此序列的一個子序列 int compareTo(String anotherString) //按字典順序比較兩個字符串 int compareToIgnoreCase(String str) //按字典順序比較兩個字符串,不考慮大小寫 boolean equalsIgnoreCase(String anotherString) //將此 String 與另一個 String 比較,不考慮大小寫 static String valueOf(double d) static String valueOf(boolean b) byte[] getBytes(Charset charset) //使用給定的 charset 將此 String 編碼到 byte 序列,并將結果存儲到新的 byte 數組 byte[] getBytes(String charsetName) //使用指定的字符集將此 String 編碼為 byte 序列,并將結果存儲到一個新的 byte 數組中 String toLowerCase(Locale locale) //使用給定 Locale 的規則將此 String 中的所有字符都轉換為小寫 String toUpperCase(Locale locale) boolean matches(String regex) //告知此字符串是否匹配給定的正則表達式 String[] split(String regex, int limit) //根據匹配給定的正則表達式來拆分此字符串 boolean startsWith(String prefix, int toffset) //測試此字符串從指定索引開始的子字符串是否以指定前綴開始 boolean endsWith(String suffix) static String copyValueOf(char[] data)//返回指定數組中表示該字符序列的 char[] toCharArray() //將此字符串轉換為一個新的字符數組 String replace(char oldChar, char newChar) //返回一個新的字符串,它是通過用 newChar 替換此字符串中出現的所有 oldChar 得到的 String replaceAll(String regex, String replacement) //使用給定的 replacement 替換此字符串所有匹配給定的正則表達式的子字符串 String intern() //返回字符串對象的規范化表示形式,字符串pool中的存在返回,不存在存入pool并返回 String trim()//返回字符串的副本,忽略前導空白和尾部空白 static String format(Locale l, String format, Object... args) //使用指定的語言環境、格式字符串和參數返回一個格式化字符串4. 不可修改的特點
以下兩點保證String的不可修改特點
value 被聲明為 final,即value引用的地址不可修改。
String類沒有暴露修改value引用內容的方法。
String類沒有方法返回本身value數組的引用。
從內存,同步和數據結構角度分析:
Requirement of String Pool:字符串池(String intern pool)是方法區域中的特殊存儲區域。 創建字符串并且池中已存在該字符串時,將返回現有字符串的引用,而不是創建新對象。如果字符串可變,這將毫無意義。
Caching Hashcode:hashcode在java中被頻繁的使用,在String類中存在屬性
private int hash;//this is used to cache hash code.
Facilitating the Use of Other Objects:確保第三方使用。舉一個例子:
//假設String.class 有屬性 value; //set的本意是保證元素不重復出現,如果String是可變的,則會破壞這個規則 HashSetset = new HashSet (); set.add(new String("a")); set.add(new String("b")); set.add(new String("c")); for(String a: set) a.value = "a";
Security:String被廣泛用作許多java類的參數,例如 網絡連接,打開文件等。字符串不是不可變的,連接或文件將被更改,這可能會導致嚴重的安全威脅。 該方法認為它連接到一臺機器,但事實并非如此。 可變字符串也可能在Reflection中引起安全問題,因為參數是字符串。例子:
boolean connect(string s){ if (!isSecure(s)) { throw new SecurityException(); } //here will cause problem, if s is changed before this by using other references. causeProblem(s); }
Immutable objects are naturally thread-safe:由于無法更改不可變對象,因此可以在多個線程之間自由共享它們。 這消除了進行同步的要求。
總之,出于效率和安全原因,String被設計為不可變的。 這也是在一般情況下在一些情況下優選不可變類的原因。
5. 字符串pool在 JAVA 語言中有8中基本類型和一種比較特殊的類型String。這些類型為了使他們在運行過程中速度更快,更節省內存,都提供了一種常量池的概念。常量池就類似一個JAVA系統級別提供的緩存。8種基本類型的常量池都是系統協調的,String類型的常量池比較特殊。它的主要使用方法有兩種:
直接使用雙引號聲明出來的String對象會直接存儲在常量池中
如果不是用雙引號聲明的String對象,可以使用String提供的intern方法。intern 方法會從字符串常量池中查詢當前字符串是否存在,若不存在就會將當前字符串放入常量池中
在 jdk6 及以前的版本中,字符串的常量池是放在堆的 Perm 區的(Perm 區是一個類靜態的區域,主要存儲一些加載類的信息,常量池,方法片段等內容,默認大小只有4m),一旦常量池中大量使用 intern 是會直接產生java.lang.OutOfMemoryError: PermGen space錯誤的。
在jdk7中,字符串常量池已經從 Perm 區移到正常的 Java Heap 區域。
它的大體實現結構就是: JAVA 使用 jni 調用c++實現的StringTable的intern方法, StringTable的intern方法跟Java中的HashMap的實現是差不多的, 只是不能自動擴容。默認大小是1009
注意點:
String的String Pool是一個固定大小的Hashtable,默認值大小長度是1009
如果放進String Pool的String非常多,就會造成Hash沖突嚴重,從而導致鏈表會很長,導致調用String.intern時性能會大幅下降(因為要一個一個找)
在 jdk6中StringTable的長度是固定 = 1009,所以如果常量池中的字符串過多就會導致效率下降很快。在jdk7中,StringTable的長度可以通過一個參數指定:-XX:StringTableSize=99991
// JDK6 中執行: false false // JDK7 中執行: false true public static void main(String[] args) { // 聲明的字符創變量 -> 堆 String s = new String("1"); s.intern(); // 聲明的字符創常量 -> 堆的 Perm 區 String s2 = "1"; System.out.println(s == s2); String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4); } // JDK6 中執行: false false // JDK7 中執行: false false public static void main(String[] args) { String s = new String("1"); String s2 = "1"; s.intern(); System.out.println(s == s2); String s3 = new String("1") + new String("1"); String s4 = "11"; s3.intern(); System.out.println(s3 == s4); }
jdk6內存分析(注:圖中綠色線條代表 string 對象的內容指向。 黑色線條代表地址指向)
String s = new String("1");Perm中的常量池 生成"1",堆中生成 變量s內容="1"
s2 -> 常量池中的"1"
String s3 = new String("1") + new String("1");Perm中的常量池 生成"1",堆中生成 兩個匿名string 內容="1" 和 變量 s3
s.intern();將"11"寫入常量池
jdk7內存分析-1
在第一段代碼中,先看 s3和s4字符串。String s3 = new String("1") + new String("1");,這句代碼中現在生成了2最終個對象,是字符串常量池中的“1” 和 JAVA Heap 中的 s3引用指向的對象。中間還有2個匿名的new String("1")我們不去討論它們。此時s3引用對象內容是”11”,但此時常量池中是沒有 “11”對象的。
接下來s3.intern();這一句代碼,是將 s3中的“11”字符串放入 String 常量池中,因為此時常量池中不存在“11”字符串,因此常規做法是跟 jdk6 圖中表示的那樣,在常量池中生成一個 “11” 的對象,關鍵點是 jdk7 中常量池不在 Perm 區域了,這塊做了調整。常量池中不需要再存儲一份對象了,可以直接存儲堆中的引用。這份引用指向 s3 引用的對象。 也就是說引用地址是相同的。
最后String s4 = "11"; 這句代碼中”11”是顯示聲明的,因此會直接去常量池中創建,創建的時候發現已經有這個對象了,此時也就是指向 s3 引用對象的一個引用。所以 s4 引用就指向和 s3 一樣了。因此最后的比較 s3 == s4 是 true。
再看 s 和 s2 對象。 String s = new String("1"); 第一句代碼,生成了2個對象。常量池中的“1” 和 JAVA Heap 中的字符串對象。s.intern(); 這一句是 s 對象去常量池中尋找后發現 “1” 已經在常量池里了。
接下來String s2 = "1"; 這句代碼是生成一個 s2的引用指向常量池中的“1”對象。 結果就是 s 和 s2 的引用地址明顯不同。圖中畫的很清晰。
jdk7內存分析-2
來看第二段代碼,從上邊第二幅圖中觀察。第一段代碼和第二段代碼的改變就是 s3.intern(); 的順序是放在String s4 = "11";后了。這樣,首先執行String s4 = "11";聲明 s4 的時候常量池中是不存在“11”對象的,執行完畢后,“11“對象是 s4 聲明產生的新對象。然后再執行s3.intern();時,常量池中“11”對象已經存在了,因此 s3 和 s4 的引用是不同的。
第二段代碼中的 s 和 s2 代碼中,s.intern();,這一句往后放也不會有什么影響了,因為對象池中在執行第一句代碼String s = new String("1");的時候已經生成“1”對象了。下邊的s2聲明都是直接從常量池中取地址引用的。 s 和 s2 的引用地址是不會相等的。
小結-從上述的例子代碼可以看出 jdk7 版本對 intern 操作和常量池都做了一定的修。主要包括2點:
將String常量池 從 Perm 區移動到了 Java Heap區
String#intern 方法時,如果存在堆中的對象,會直接保存對象的引用,而不會重新創建對象。
參考:
https://tech.meituan.com/2014...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/75571.html
摘要:哪吒社區技能樹打卡打卡貼函數式接口簡介領域優質創作者哪吒公眾號作者架構師奮斗者掃描主頁左側二維碼,加入群聊,一起學習一起進步歡迎點贊收藏留言前情提要無意間聽到領導們的談話,現在公司的現狀是碼農太多,但能獨立帶隊的人太少,簡而言之,不缺干 ? 哪吒社區Java技能樹打卡?【打卡貼 day2...
摘要:資源獲取方式根據下面的索引,大家可以選擇自己需要的資源,然后在松哥公眾號牧碼小子后臺回復對應的口令,就可以獲取到資源的百度云盤下載地址。公眾號二維碼如下另外本文會定期更新,松哥有新資源的時候會及時分享給大家,歡迎各位小伙伴保持關注。 沒有一條路是容易的,特別是轉行計算機這條路。 松哥接觸過很多轉行做開發的小伙伴,我了解到很多轉行人的不容易,記得松哥大二時剛剛決定轉行計算機,完全不知道這...
摘要:再附一部分架構面試視頻講解本文已被開源項目學習筆記總結移動架構視頻大廠面試真題項目實戰源碼收錄 Java反射(一)Java反射(二)Java反射(三)Java注解Java IO(一)Java IO(二)RandomAccessFileJava NIOJava異常詳解Java抽象類和接口的區別Java深拷貝和淺拷...
摘要:基礎問題的的性能及原理之區別詳解備忘筆記深入理解流水線抽象關鍵字修飾符知識點總結必看篇中的關鍵字解析回調機制解讀抽象類與三大特征時間和時間戳的相互轉換為什么要使用內部類對象鎖和類鎖的區別,,優缺點及比較提高篇八詳解內部類單例模式和 Java基礎問題 String的+的性能及原理 java之yield(),sleep(),wait()區別詳解-備忘筆記 深入理解Java Stream流水...
摘要:基礎問題的的性能及原理之區別詳解備忘筆記深入理解流水線抽象關鍵字修飾符知識點總結必看篇中的關鍵字解析回調機制解讀抽象類與三大特征時間和時間戳的相互轉換為什么要使用內部類對象鎖和類鎖的區別,,優缺點及比較提高篇八詳解內部類單例模式和 Java基礎問題 String的+的性能及原理 java之yield(),sleep(),wait()區別詳解-備忘筆記 深入理解Java Stream流水...
閱讀 3398·2023-04-25 22:04
閱讀 2197·2021-11-22 15:29
閱讀 2161·2021-10-11 10:57
閱讀 1402·2021-09-24 09:48
閱讀 3147·2021-09-09 09:34
閱讀 2543·2021-09-02 15:21
閱讀 2393·2019-08-30 15:53
閱讀 1120·2019-08-30 14:07