String
String是一個很普通的類
原始碼分析
//該值用於字元儲存
private final char value[];
//快取字串的雜湊碼
private int hash;// Default to 0
//這個是一個建構函式
//把傳遞進來的字串物件value這個陣列的值,
//賦值給構造的當前物件,hash的處理方式也一樣。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
//String的初始化有很多種
//空引數初始化
//String初始化
//字元陣列初始化
//位元組陣列初始化
//通過StringBuffer,StringBuilder構造
問題: 我現正在準備構造一個String的物件,那original這個物件又是從何而來?是什麼時候構造的呢?
測試一下:
public static void main(String[] args) {
String str = new String("zwt");
String str1 = new String("zwt");
}
在Java中,當值被雙引號引起來(如本示例中的"abc"),JVM會去先檢檢視一看常量池裡有沒有abc這個物件,
如果沒有,把abc初始化為物件放入常量池,如果有,直接返回常量池內容。
Java字串兩種宣告方式在堆記憶體中不同的體現,
為了避免重複的建立物件,儘量使用String s1 ="123" 而不是String s1 = new String("123"),因為JVM對前者給做了優化。
常用的API
System.out.println(str.isEmpty());//判斷是不是空字串
System.out.println(str.length());//獲取字串長度
System.out.println(str.charAt(1));//獲取指定位置的字元
System.out.println(str.substring(2, 3));//擷取指定區間字串
System.out.println(str.equals(str1));//比較字串
isEmpty()
public boolean isEmpty() {
return value.length == 0;
}
length()
public int length() {
return value.length;
}
charAt()
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
substring()
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
//如果擷取的開始範圍剛好是0並且結束範圍等於陣列的長度,直接返回當前物件,
//否則用該陣列和傳入的開始範圍和結束範圍重新構建String物件並返回。
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
equals()
public boolean equals(Object anObject) {
//如果是同一個引用,直接返回true
if (this == anObject) {
return true;
}
//判斷是否是String
if (anObject instanceof String) {
//判斷長度是否一致
String anotherString = (String)anObject;
int n = value.length;
//判斷char[]裡面的每一個值是否相等
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
equals()與“==”
這兩者之間沒有必然的聯絡,
在引用型別中,"=="是比較兩個引用是否指向堆記憶體裡的同一個地址(同一個物件),
而equals是一個普通的方法,該方法返回的結果依賴於自身的實現。
intern()
public native String intern();
//如果常量池中有當前String的值,就返回這個值,如果沒有就加進去,返回這個值的引用,
一些基礎
Java基本資料型別和引用型別
Java中一共有四類八種基本資料型別, 除掉這四類八種基本型別,其它的都是物件,也就是引用型別。
基本資料型別 | |
---|---|
浮點型別 | float double |
字元型 | char |
邏輯型 | boolean |
整型 | byte short int long |
Java自動裝箱/拆箱
Integer 裡面我們曾經說過得 valueOf (), 這個加上valueOf方法的過程,就是Java中經常說的裝箱過程。
在JDK1.5中,給這四類八種基本型別加入了包裝類 。
第一類:整型
byte Byte
short Short
int Integer
long Long
第二類:浮點型
float Float
double Double
第三類:邏輯型
boolean Boolean
第四類:字元型
char Character
將int的變數轉換成Integer物件,這個過程叫做裝箱,
反之將Integer物件轉換成int型別值,這個過程叫做拆箱。
以上這些裝箱拆箱的方法是在編譯成class檔案時自動加上的,不需要程式設計師手工介入,因此又叫自動裝箱/拆箱。
用處:
1、物件是對現實世界的模擬 。
2、為泛型提供了支援。
3、提供了豐富的屬性和API
public static void main(String[] args) {
int int1 = 180;
Integer int2 = new Integer(180);
}
表現如下圖:
StringBuilder
StringBuilder類被 final 所修飾,因此不能被繼承。
StringBuilder類繼承於 AbstractStringBuilder類。
實際上,AbstractStringBuilder類具體實現了可變字元序列的一系列操作,
比如:append()、insert()、delete()、replace()、charAt()方法等。
值得一提的是,StringBuffer也是繼承於AbstractStringBuilder類。
StringBuilder類實現了2個介面:
Serializable 序列化介面,表示物件可以被序列化。
CharSequence 字元序列介面,提供了幾個對字元序列進行只讀訪問的方法,
比如:length()、charAt()、subSequence()、toString()方法等。
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
定義的常量
//toString 返回的最後一個值的快取。每當修改 StringBuffer 時清除。
private transient char[] toStringCache;
AbstractStringBuilder
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
//value 用來儲存字元序列中的字元。value是一個動態的陣列,當儲存容量不足時,會對它進行擴容。
char[] value;
/**
* The count is the number of characters used.
*/
//count 表示value陣列中已儲存的字元數。
int count;
構造方法
public StringBuilder() {
super(16);
}
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
// AbstractStringBuilder.java
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
StringBuilder類提供了4個構造方法。構造方法主要完成了對value陣列的初始化。
其中:
- 預設構造方法設定了value陣列的初始容量為16。
- 第2個構造方法設定了value陣列的初始容量為指定的大小。
- 第3個構造方法接受一個String物件作為引數,設定了value陣列的初始容量為String物件的長度+16,並把String物件中的字元新增到value陣列中。
- 第4個構造方法接受一個CharSequence物件作為引數,設定了value陣列的初始容量為CharSequence物件的長度+16,並把CharSequence物件中的字元新增到value陣列中。
append()方法
有多種實現,一般的順序為:
append() ----> ensureCapacityInternal() 確保value陣列有足夠的容量 ----> newCapacity()新的容量
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
//擴容引數
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
在上面程式碼中可以看到具體的擴容規則是 * 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