StringBuilder原始碼分析,讓你不用再死記答案
1、前言
相信學習Java的小夥伴對String、StringBuilder、StringBuffer都不會陌生,幾乎每天都和它們打交道,但是你是否真的瞭解其它們呢?雖然看似簡單的基礎知識,但是確實高頻的面試點。很多同學背過相關面試題,但是很容易就會忘記,主要是沒有隻有背、少了理解,因此很容易就會給忘記了。
2、StringBuilder的簡單使用
StringBuilder sb=new StringBuilder();
sb.append("hello");
sb.append("world");
System.out.println(sb.toString());
是不是非常的熟悉,StringBuilder可以用來拼接字串。那麼大家有沒有相關它底層是如何實現的呢?
3、類的關係梳理
第一,定義一個介面
public interface CharSequence {
//獲取內容長度
int length();
//定位某個字元
char charAt(int index);
//擷取字串
CharSequence subSequence(int start, int end);
//toString
public String toString();
}
第二,定義一個抽象類
abstract class AbstractStringBuilder implements Appendable, CharSequence {
//陣列字元的陣列
char[] value;
//字元長度
int count;
//建構函式(傳遞容量大小)
AbstractStringBuilder(int capacity) {
//初始化一個陣列
value = new char[capacity];
}
//返回長度
public int length() {
return count;
}
//返回容量大小
public int capacity() {
return value.length;
}
}
第三步,定義一個實現類
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
//建構函式
public StringBuilder() {
//如果不指定容量值,則預設是16
super(16);
}
//建構函式
public StringBuilder(int capacity) {
super(capacity);
}
}
透過以上的簡單類結構,我們需要掌握以下幾個核心問題
①什麼需要加一個抽象類,AbstractStringBuilder呢?為什麼不直接實現類實現介面呢?
②StringBuilder底層是char[]陣列,它的資料結構是陣列,如果不指定則預設是16
③為什麼需要count欄位呢?length()方法和capacity()的區別是什麼呢?這不是多餘的嗎?
溫馨提示:我們看原始碼的時候,看的過程帶著問題、帶著疑問去原始碼當中尋找答案,這樣才能有所收穫,而不是為了看原始碼而去看原始碼。
一般來說,抽象類可以用來解耦介面和實現類,很多優秀的框架幾乎都是這種模式,好處是把公共的部分抽取到抽象類實現,減輕實現類的操作,具體如下所示。
//介面
public interface ITest{
public void sayHello(String name);
}
//實現類
public class TestImpl implements ITest{
@Override
public void sayHello(String name){
//對name進行校驗
if(name!=null&&!"".equals(name)){
//具體的業務處理
}
}
}
這種模式的缺點就是如果ITest有好多個實現類,每個實現類都需要對name欄位進行校驗。那麼如何最佳化呢?
//介面
public interface ITest{
public void sayHello(String name);
}
//抽象類
public class AbstractTestImpl implements ITest{
@Override
public void sayHello(String name){
//對name進行校驗
if(name!=null&&!"".equals(name)){
//呼叫抽象方法
say(name);
}
}
//定義一個抽象方法
public abstract void say(String name);
}
//實現類
public class TestImpl extends AbstractTestImpl{
public void say(String name){
//無需要做校驗,直接開始處理業務即可
}
}
看到這裡,是不是明白為什麼需要加AbstractStringBuilder類了呢?
4、StringBuilder儲存資料
AbstractStringBuilder類的append方法解析
public AbstractStringBuilder append(String str) {
if (str == null){
str = "null";
}
//1.獲取追加字串的長度
int len = str.length();
//2.動態擴容char[]陣列【陣列長度是count+len】【繼續看原始碼】
ensureCapacityInternal(count + len);
//3.往擴容char[]陣列新增內容
str.getChars(0, len, value, count);
//4.count屬性累加
count += len;
return this;
}
StringBuilder類的append方法解析
public StringBuilder append(String str) {
//它本身不處理,只是呼叫父類的方法
super.append(str);
return this;
}
那麼如何擴容的呢?
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
}
void expandCapacity(int minimumCapacity) {
//1.新陣列的長度是原來的兩倍
int newCapacity = value.length * 2 + 2;
//2.判斷兩個值,最後覺得以哪個長度為主
if (newCapacity - minimumCapacity < 0){
newCapacity = minimumCapacity;
}
//3.判斷新陣列長度是否為小於0
if (newCapacity < 0) {
if (minimumCapacity < 0){
throw new OutOfMemoryError();
}
//如果小於0則取Integer的最大值
newCapacity = Integer.MAX_VALUE;
}
//4.複製陣列【繼續看原始碼】
value = Arrays.copyOf(value, newCapacity);
}
思考:newCapacity為什麼會小於0呢?是不是很奇怪呢?
解析:因為int是32位的二進位制,最高位是符號位(0正數,1負數),如果newCapacity大於Integer最大值,那麼首位被擠掉了,由0變成1,那麼就變成了負數了。
public static char[] copyOf(char[] original, int newLength) {
//1.建立一個新的陣列
char[] copy = new char[newLength];
//2.複製陣列【繼續看原始碼】
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
System.arraycopy是JVM底層提供的方法,native修飾的,用它來進行陣列之間的複製。
原始碼分析到這裡,我們必須掌握兩個核心的東西
第一)StringBuilder底層是char[]陣列
第二)StringBuilder是動態擴容的,它是透過建立一個新的陣列,然後把舊陣列的資料複製到新陣列當中,舊陣列給gc回收
5、StringBuilder刪除資料
public AbstractStringBuilder deleteCharAt(int index) {
//1.校驗
if ((index < 0) || (index >= count)){
throw new StringIndexOutOfBoundsException(index);
}
//2.複製陣列【同一個陣列之間的複製】
System.arraycopy(value, index+1, value, index, count-index-1);
//3.count遞減
count--;
return this;
}
複製這個地方思路可能有點繞,給大家重點分析一下
char[] arrs={0,1,2,3,4,5,6},有7個元素,我們要刪除index=4的元素,那麼如何刪除呢?思路是arrs陣列直接的複製,
System.arraycopy(src,srcPos,dest,destPos,length)
①src表示源陣列
②srcPos表示從源陣列的什麼位置開始複製
③dest表示複製到哪個陣列
④destPos表示複製到新陣列的哪個位置
⑤length表示複製舊陣列的幾個元素
System.arraycopy(arrs,index+1,arrs,index,arrs.length-1-index)會執行如下操作
①0,1,2,3元素不變
②第5個元素放的是5
③第6個元素放的是6
④第七個元素是4,4會被擠到最後【大家可以測試該函式的使用】
最後結果是,char[] arrs={0,1,2,3,5,6,4}
4就是被我們刪除的元素,我們只能透過把它擠到最後,然後透過count–表示陣列的有效長度,那麼讀取陣列的時候不是
for(int i=0;i<arrs.length;i++)
,而是for(int i=0;i<count;i++)
因此,大家回過頭去看前面的問題,為什麼需要count欄位,length()和capacity()方法的區別,是不是就恍然大悟了。
6、總結
看到這裡StringBuilder的核心思路就講解完成了,它有很多的方法,大家可以自己去看,其實都不難,掌握核心思想即可。
第一)它底層是char[]陣列
第二)新增元素的時候,如何擴容陣列
第三)刪除元素的時候,又是如何處理的
慕課網專欄(架構思想之微服務+仿百度網盤原始碼):
感謝您的閱讀,希望您多多支援,這樣我在原創的道路上才能更加的有信心。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3407/viewspace-2825350/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 死磕 jdk原始碼之HashMap原始碼分析JDK原始碼HashMap
- 從JDK原始碼看StringBuilderJDK原始碼UI
- 死磕 java集合之TreeSet原始碼分析Java原始碼
- 死磕 java集合之WeakHashMap原始碼分析JavaHashMap原始碼
- 死磕 java集合之LinkedList原始碼分析Java原始碼
- 死磕 java集合之ConcurrentLinkedQueue原始碼分析Java原始碼
- 死磕 java集合之PriorityQueue原始碼分析Java原始碼
- 死磕 java集合之ArrayList原始碼分析Java原始碼
- 死磕 java集合之HashMap原始碼分析JavaHashMap原始碼
- 死磕 java集合之CopyOnWriteArrayList原始碼分析Java原始碼
- 死磕 java集合之LinkedHashMap原始碼分析JavaHashMap原始碼
- 死磕以太坊原始碼分析之state原始碼
- 死磕以太坊原始碼分析之txpool原始碼
- 筆記-runtime原始碼解析之讓你徹底瞭解底層原始碼筆記原始碼
- 死磕以太坊原始碼分析之挖礦流程分析原始碼
- 死磕 java集合之ConcurrentHashMap原始碼分析(一)JavaHashMap原始碼
- 死磕以太坊原始碼分析之downloader同步原始碼
- 死磕以太坊原始碼分析之Fetcher同步原始碼
- JDK原始碼閱讀(3):AbstractStringBuilder、StringBuffer、StringBuilder類閱讀筆記JDK原始碼UI筆記
- JAVA面試題 StringBuffer和StringBuilder的區別,從原始碼角度分析?Java面試題UI原始碼
- 死磕 java併發包之AtomicInteger原始碼分析Java原始碼
- 死磕 java併發包之LongAdder原始碼分析Java原始碼
- 死磕以太坊原始碼分析之MPT樹-上原始碼
- 死磕以太坊原始碼分析之rlpx協議原始碼協議
- ClickHouse原始碼筆記5:聚合函式的原始碼再梳理原始碼筆記函式
- FutureTask原始碼分析筆記原始碼筆記
- 原始碼分析筆記——OkHttp原始碼筆記HTTP
- 死磕以太坊原始碼分析之Kademlia演算法原始碼演算法
- 死磕以太坊原始碼分析之EVM指令集原始碼
- 從原始碼看String,StringBuffer,StringBuilder的區別原始碼UI
- 死磕 java集合之TreeMap原始碼分析(四)-內含彩蛋Java原始碼
- 死磕 java集合之ConcurrentSkipListMap原始碼分析——發現個bugJava原始碼
- 死磕原始碼分析,只有頭髮少的人才研究過原始碼
- 故障分析 | 從 Insert 併發死鎖分析 Insert 加鎖原始碼邏輯原始碼
- 不懂大資料分析?不用怕,永洪讓你點點滑鼠就可以了大資料
- 死磕 java集合之ConcurrentHashMap原始碼分析(二)——擴容全解析JavaHashMap原始碼
- 死磕 java集合之ConcurrentHashMap原始碼分析(一)——插入元素全解析JavaHashMap原始碼
- 死磕以太坊原始碼分析之區塊上鍊入庫原始碼