String

String是一個很普通的類

原始碼分析

  1. //該值用於字元儲存
  2. private final char value[];
  3. //快取字串的雜湊碼
  4. private int hash;// Default to 0
  5. //這個是一個建構函式
  6. //把傳遞進來的字串物件value這個陣列的值,
  7. //賦值給構造的當前物件,hash的處理方式也一樣。
  8. public String(String original) {
  9. this.value = original.value;
  10. this.hash = original.hash;
  11. }
  12. //String的初始化有很多種
  13. //空引數初始化
  14. //String初始化
  15. //字元陣列初始化
  16. //位元組陣列初始化
  17. //通過StringBuffer,StringBuilder構造

問題: 我現正在準備構造一個String的物件,那original這個物件又是從何而來?是什麼時候構造的呢?

測試一下:

  1. public static void main(String[] args) {
  2. String str = new String("zwt");
  3. String str1 = new String("zwt");
  4. }

在Java中,當值被雙引號引起來(如本示例中的"abc"),JVM會去先檢檢視一看常量池裡有沒有abc這個物件,

如果沒有,把abc初始化為物件放入常量池,如果有,直接返回常量池內容。

Java字串兩種宣告方式在堆記憶體中不同的體現

為了避免重複的建立物件,儘量使用String s1 ="123" 而不是String s1 = new String("123"),因為JVM對前者給做了優化。

常用的API

  1. System.out.println(str.isEmpty());//判斷是不是空字串
  2. System.out.println(str.length());//獲取字串長度
  3. System.out.println(str.charAt(1));//獲取指定位置的字元
  4. System.out.println(str.substring(2, 3));//擷取指定區間字串
  5. System.out.println(str.equals(str1));//比較字串

isEmpty()

  1. public boolean isEmpty() {
  2. return value.length == 0;
  3. }

length()

  1. public int length() {
  2. return value.length;
  3. }

charAt()

  1. public char charAt(int index) {
  2. if ((index < 0) || (index >= value.length)) {
  3. throw new StringIndexOutOfBoundsException(index);
  4. }
  5. return value[index];
  6. }

substring()

  1. public String substring(int beginIndex) {
  2. if (beginIndex < 0) {
  3. throw new StringIndexOutOfBoundsException(beginIndex);
  4. }
  5. int subLen = value.length - beginIndex;
  6. if (subLen < 0) {
  7. throw new StringIndexOutOfBoundsException(subLen);
  8. }
  9. //如果擷取的開始範圍剛好是0並且結束範圍等於陣列的長度,直接返回當前物件,
  10. //否則用該陣列和傳入的開始範圍和結束範圍重新構建String物件並返回。
  11. return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
  12. }

equals()

  1. public boolean equals(Object anObject) {
  2. //如果是同一個引用,直接返回true
  3. if (this == anObject) {
  4. return true;
  5. }
  6. //判斷是否是String
  7. if (anObject instanceof String) {
  8. //判斷長度是否一致
  9. String anotherString = (String)anObject;
  10. int n = value.length;
  11. //判斷char[]裡面的每一個值是否相等
  12. if (n == anotherString.value.length) {
  13. char v1[] = value;
  14. char v2[] = anotherString.value;
  15. int i = 0;
  16. while (n-- != 0) {
  17. if (v1[i] != v2[i])
  18. return false;
  19. i++;
  20. }
  21. return true;
  22. }
  23. }
  24. return false;
  25. }

equals()與“==”

這兩者之間沒有必然的聯絡,

在引用型別中,"=="是比較兩個引用是否指向堆記憶體裡的同一個地址(同一個物件),

equals是一個普通的方法,該方法返回的結果依賴於自身的實現

intern()

  1. public native String intern();
  2. //如果常量池中有當前String的值,就返回這個值,如果沒有就加進去,返回這個值的引用,

一些基礎

Java基本資料型別和引用型別

Java中一共有四類八種基本資料型別, 除掉這四類八種基本型別,其它的都是物件,也就是引用型別。

基本資料型別
浮點型別 float double
字元型 char
邏輯型 boolean
整型 byte short int long

Java自動裝箱/拆箱

Integer 裡面我們曾經說過得 valueOf (), 這個加上valueOf方法的過程,就是Java中經常說的裝箱過程。

在JDK1.5中,給這四類八種基本型別加入了包裝類 。

  1. 第一類:整型
  2. byte Byte
  3. short Short
  4. int Integer
  5. long Long
  6. 第二類:浮點型
  7. float Float
  8. double Double
  9. 第三類:邏輯型
  10. boolean Boolean
  11. 第四類:字元型
  12. char Character

將int的變數轉換成Integer物件,這個過程叫做裝箱,

反之將Integer物件轉換成int型別值,這個過程叫做拆箱。

以上這些裝箱拆箱的方法是在編譯成class檔案時自動加上的,不需要程式設計師手工介入,因此又叫自動裝箱/拆箱。

用處:

1、物件是對現實世界的模擬 。

2、為泛型提供了支援。

3、提供了豐富的屬性和API

  1. public static void main(String[] args) {
  2. int int1 = 180;
  3. Integer int2 = new Integer(180);
  4. }

表現如下圖:

StringBuilder

StringBuilder類被 final 所修飾,因此不能被繼承。

StringBuilder類繼承於 AbstractStringBuilder類。

實際上,AbstractStringBuilder類具體實現了可變字元序列的一系列操作,

比如:append()、insert()、delete()、replace()、charAt()方法等。

值得一提的是,StringBuffer也是繼承於AbstractStringBuilder類。

StringBuilder類實現了2個介面:

Serializable 序列化介面,表示物件可以被序列化。

CharSequence 字元序列介面,提供了幾個對字元序列進行只讀訪問的方法,

比如:length()、charAt()、subSequence()、toString()方法等。

  1. public final class StringBuffer
  2. extends AbstractStringBuilder
  3. implements java.io.Serializable, CharSequence

定義的常量

  1. //toString 返回的最後一個值的快取。每當修改 StringBuffer 時清除。
  2. private transient char[] toStringCache;

AbstractStringBuilder

  1. abstract class AbstractStringBuilder implements Appendable, CharSequence {
  2. /**
  3. * The value is used for character storage.
  4. */
  5. //value 用來儲存字元序列中的字元。value是一個動態的陣列,當儲存容量不足時,會對它進行擴容。
  6. char[] value;
  7. /**
  8. * The count is the number of characters used.
  9. */
  10. //count 表示value陣列中已儲存的字元數。
  11. int count;

構造方法

  1. public StringBuilder() {
  2. super(16);
  3. }
  4. public StringBuilder(int capacity) {
  5. super(capacity);
  6. }
  7. public StringBuilder(String str) {
  8. super(str.length() + 16);
  9. append(str);
  10. }
  11. public StringBuilder(CharSequence seq) {
  12. this(seq.length() + 16);
  13. append(seq);
  14. }
  15. // AbstractStringBuilder.java
  16. AbstractStringBuilder(int capacity) {
  17. value = new char[capacity];
  18. }

StringBuilder類提供了4個構造方法。構造方法主要完成了對value陣列的初始化。

其中:

  1. 預設構造方法設定了value陣列的初始容量為16。
  2. 第2個構造方法設定了value陣列的初始容量為指定的大小。
  3. 第3個構造方法接受一個String物件作為引數,設定了value陣列的初始容量為String物件的長度+16,並把String物件中的字元新增到value陣列中。
  4. 第4個構造方法接受一個CharSequence物件作為引數,設定了value陣列的初始容量為CharSequence物件的長度+16,並把CharSequence物件中的字元新增到value陣列中。

append()方法

有多種實現,一般的順序為:

append() ----> ensureCapacityInternal() 確保value陣列有足夠的容量 ----> newCapacity()新的容量

  1. private void ensureCapacityInternal(int minimumCapacity) {
  2. // overflow-conscious code
  3. if (minimumCapacity - value.length > 0) {
  4. value = Arrays.copyOf(value,
  5. newCapacity(minimumCapacity));
  6. }
  7. }
  8. private int newCapacity(int minCapacity) {
  9. // overflow-conscious code
  10. //擴容引數
  11. int newCapacity = (value.length << 1) + 2;
  12. if (newCapacity - minCapacity < 0) {
  13. newCapacity = minCapacity;
  14. }
  15. return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
  16. ? hugeCapacity(minCapacity)
  17. : newCapacity;
  18. }

在上面程式碼中可以看到具體的擴容規則是 * 2 + 2

StringBuffer

基本就是加了一個synchronized的StringBuilder。

StringBuilder 和 StringBuffer 適用的場景是什麼?

stringbuffer固然是執行緒安全的,stringbuffer固然是比stringbuilder更慢,固然,在多執行緒的情況下,理論上是應該使用執行緒安全的stringbuffer的。

實際上基本沒有什麼地方顯示你需要一個執行緒安全的string拼接器 。

stringbuffer基本沒有適用場景,你應該在所有的情況下選擇使用stringbuiler,除非你真的遇到了一個需要執行緒安全的場景 。

如果你遇見了,,,

stringbuffer的執行緒安全,僅僅是保證jvm不丟擲異常順利的往下執行而已,它可不保證邏輯正確和呼叫順序正確。大多數時候,我們需要的不僅僅是執行緒安全,而是鎖。

最後,為什麼會有stringbuffer的存在,如果真的沒有價值,為什麼jdk會提供這個類?

答案太簡單了,因為最早是沒有stringbuilder的,sun的人不知處於何種考慮,決定讓stringbuffer是執行緒安全的,

於是,在jdk1.5的時候,終於決定提供一個非執行緒安全的stringbuffer實現,並命名為stringbuilder。

順便,javac好像大概也是從這個版本開始,把所有用加號連線的string運算都隱式的改寫成stringbuilder,

也就是說,從jdk1.5開始,用加號拼接字串已經幾乎沒有什麼效能損失了。

擴充套件小知識

Java9改進了字串(包括String、StringBuffer、StringBuilder)的實現。

在Java9以前字串採用char[]陣列來儲存字元,因此字串的每個字元佔2位元組,

而Java9的字串採用byte[]陣列再加一個encoding-flag欄位來儲存字元,因此字串的每個字元只佔1位元組。

所以Java9的字串更加節省空間,字串的功能方法也沒有受到影響。

參考連結

https://zhuanlan.zhihu.com/p/28216267

https://blog.csdn.net/u012317510/article/details/83721250

https://www.zhihu.com/question/20101840