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