Java核心 -- 字串
-
String是Java語言非常基礎
和重要
的類,提供了構造和管理字串的各種基本邏輯,是典型的Immutable
類
- String是Immutable類的典型實現,原生的保證了基礎執行緒安全 ,因為無法對它內部資料進行任何修改
- 被宣告為final class,由於String的不可變 性,類似拼接、裁剪字串等動作,都會產生新的String物件
- 由於字串操作的普遍性,所以相關操作的效率往往對應用效能 有明顯影響
StringBuffer
- StringBuffer是為了解決拼接產生太多中間物件的問題而提供的一個類
- StringBuffer本質是一個執行緒安全 的可修改字串序列,保證了執行緒安全,但也帶來了額外的效能開銷
-
StringBuffer的執行緒安全是通過把各種修改資料的方法都加上synchronized關鍵字實現的
- 這種方式非常適合常見的執行緒安全類的實現,不必糾結於synchronized的效能
- 過早的優化是萬惡之源 ,可靠性、正確性和程式碼可讀性才是大多數應用開發的首要考慮因素
StringBuilder
- StringBuilder在能力上和StringBuffer沒有本質區別,但去掉了執行緒安全的部分,有效減小了開銷
-
StringBuffer和StringBuilder底層都是利用可修改的陣列(Java 8為char[],Java 9為byte[]),都繼承AbstractStringBuilder
- 區別僅在於最終的方法是否有synchronized關鍵字
-
內部陣列初始大小為初始字串長度+16
- 如果確定拼接會發生多次,並且是可預計的,最好指定合適的初始大小 ,避免多次擴容的開銷 (arraycopy)
字串拼接
public class StringConcat { public static void main(String[] args) { String str = "aa" + "bb" + "cc" + "dd"; System.out.println("str : " + str); } }
先用javac 編譯,再用javap 反編譯
Java 8
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=2, args_size=1 0: ldc#2// String aabbccdd 2: astore_1 3: getstatic#3// Field java/lang/System.out:Ljava/io/PrintStream; 6: new#4// class java/lang/StringBuilder 9: dup 10: invokespecial #5// Method java/lang/StringBuilder."<init>":()V 13: ldc#6// String str : 15: invokevirtual #7// Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 18: aload_1 19: invokevirtual #7// Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 22: invokevirtual #8// Method java/lang/StringBuilder.toString:()Ljava/lang/String; 25: invokevirtual #9// Method java/io/PrintStream.println:(Ljava/lang/String;)V 28: return
- “aa” + “bb” + “cc” + “dd”會被當成常量”aabbccdd”
- 字串的拼接操作會自動被javac轉換成StringBuilder操作
Java 11
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: ldc#2// String aabbccdd 2: astore_1 3: getstatic#3// Field java/lang/System.out:Ljava/io/PrintStream; 6: aload_1 7: invokedynamic #4,0// InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String; 12: invokevirtual #5// Method java/io/PrintStream.println:(Ljava/lang/String;)V 15: return
- “aa” + “bb” + “cc” + “dd”同樣會被當成常量”aabbccdd”
- Java 11為了更加統一字串操作優化,提供了StringConcatFactory,作為一個統一的入口
- javac自動生成的程式碼,未必是最優的,但針對普通場景已經足夠了
字串快取
-
把常見應用進行Heap Dump,然後分析物件組成,大約25%的物件是字串,並且其中約50%是重複
的
- 如果能避免建立重複字串,可以有效降低記憶體消耗 和物件建立開銷
-
String在Java 6提供了intern(),目的是提示JVM把相應的字串快取起來
- 建立String物件並且呼叫intern(),如果已經有快取的字串,就會返回快取裡的例項,否則將其快取起來
- 被快取的字串會儲存在PermGen (永久代),PermGen的空間非常有限 ,只有FullGC 會處理PermGen
- 所以,如果使用不當,會觸發OOM
- 另外,intern()是一種顯式 地排重機制,但這也是一種 程式碼汙染
-
在後續的Java版本中,字串快取被放置在堆
中,極大的避免了PermGen佔滿的問題
- 在Java 8中被MetaSpace (元資料區)取代了
-
預設快取大小也在不斷地擴大,可以通過
-XX:+PrintStringTableStatistics
檢視-
也可以通過
-XX:StringTableSize=N
調整大小,但絕大部分情況下不需要調整
-
也可以通過
-
在Oracle JDK 8u20出現了G1 GC的字串排重,通過將相同資料的字串指向同一份資料
來實現的
-XX:+UseStringDeduplication
$ java -XX:+PrintStringTableStatistics -version java version "1.8.0_191" Java(TM) SE Runtime Environment (build 1.8.0_191-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode) SymbolTable statistics: Number of buckets:20011 =160088 bytes, avg8.000 Number of entries:9616 =230784 bytes, avg24.000 Number of literals:9616 =380296 bytes, avg39.548 Total footprint:=771168 bytes Average bucket size:0.481 Variance of bucket size :0.483 Std. dev. of bucket size:0.695 Maximum bucket size:5 StringTable statistics: Number of buckets:60013 =480104 bytes, avg8.000 Number of entries:672 =16128 bytes, avg24.000 Number of literals:672 =45472 bytes, avg67.667 Total footprint:=541704 bytes Average bucket size:0.011 Variance of bucket size :0.011 Std. dev. of bucket size:0.106 Maximum bucket size:2
Intrinsic
-
在執行時,字串的一些基礎操作會直接利用JVM內部的Intrinsic機制
- 往往執行的是特殊優化的原生代碼 ,而不是Java程式碼生成的位元組碼
- Intrinsic:是一種利用native方式hard-coded 的邏輯,算是一種特殊的內聯,很多優化還需要使用特定的CPU指令
-
通過
-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
檢視
$ java -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -version 6413java.lang.String::hashCode (55 bytes) 6623java.lang.String::charAt (29 bytes) @ 18java/lang/StringIndexOutOfBoundsException::<init> (not loaded)not inlineable 6733java.lang.String::length (6 bytes) 684n 0java.lang.System::arraycopy (native)(static) 6853java.lang.String::equals (81 bytes) java version "1.8.0_191" Java(TM) SE Runtime Environment (build 1.8.0_191-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
字串壓縮
- 在Java的歷史版本中,使用了char[]來儲存資料
- 但char佔用2個byte ,而拉丁語系語言的字元,不需要太寬的char,這會造成一定的浪費
-
在Java 9中引入了Compact Strings
的設計
- 將儲存方式從char[]陣列改變為一個byte[]加上一個標識編碼的coder
- 並且將相關字串操作類都進行了修改,所有相關的Intrinsic都進行了重寫,保證沒有任何效能損失
- 該特性對絕大部分應用來說是透明 的
轉載請註明出處:http://zhongmingmao.me/2019/04/27/java-core-string/
訪問原文「Java核心 -- 字串」獲取最佳閱讀體驗並參與討論