程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 計算機程序的思維邏輯 (30),思維邏輯

計算機程序的思維邏輯 (30),思維邏輯

編輯:JAVA綜合教程

計算機程序的思維邏輯 (30),思維邏輯


上節介紹了String,提到如果字符串修改操作比較頻繁,應該采用StringBuilder和StringBuffer類,這兩個類的方法基本是完全一樣的,它們的實現代碼也幾乎一樣,唯一的不同就在於,StringBuffer是線程安全的,而StringBuilder不是。

線程以及線程安全的概念,我們在後續章節再詳細介紹。這裡需要知道的就是,線程安全是有成本的,影響性能,而字符串對象及操作,大部分情況下,沒有線程安全的問題,適合使用StringBuilder。所以,本節就只討論StringBuilder。

StringBuilder的基本用法也是很簡單的,我們來看下。

基本用法

創建StringBuilder

StringBuilder sb = new StringBuilder();

添加字符串,通過append方法

sb.append("老馬說編程");
sb.append(",探索編程本質");

獲取構建後的字符串,通過toString方法

System.out.println(sb.toString());

輸出為:

老馬說編程,探索編程本質

大部分情況,使用就這麼簡單,通過new新建StringBuilder,通過append添加字符串,然後通過toString獲取構建完成的字符串。

StringBuilder是怎麼實現的呢?

基本實現原理

內部組成和構造方法

與String類似,StringBuilder類也封裝了一個字符數組,定義如下:

char[] value;

與String不同,它不是final的,可以修改。另外,與String不同,字符數組中不一定所有位置都已經被使用,它有一個實例變量,表示數組中已經使用的字符個數,定義如下:

int count;

StringBuilder繼承自AbstractStringBuilder,它的默認構造方法是:

public StringBuilder() {
    super(16);
}

調用父類的構造方法,父類對應的構造方法是:

AbstractStringBuilder(int capacity) {
    value = new char[capacity];
}

也就是說,new StringBuilder()這句代碼,內部會創建一個長度為16的字符數組,count的默認值為0。

append的實現

來看append的代碼:

public AbstractStringBuilder append(String str) {
    if (str == null) str = "null";
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}

append會直接拷貝字符到內部的字符數組中,如果字符數組長度不夠,會進行擴展,實際使用的長度用count體現。具體來說,ensureCapacityInternal(count+len)會確保數組的長度足以容納新添加的字符,str.getChars會拷貝新添加的字符到字符數組中,count+=len會增加實際使用的長度。

ensureCapacityInternal的代碼如下:

private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0)
        expandCapacity(minimumCapacity);
}

如果字符數組的長度小於需要的長度,則調用expandCapacity進行擴展,expandCapacity的代碼是:

void expandCapacity(int minimumCapacity) {
    int newCapacity = value.length * 2 + 2;
    if (newCapacity - minimumCapacity < 0)
        newCapacity = minimumCapacity;
    if (newCapacity < 0) {
        if (minimumCapacity < 0) // overflow
            throw new OutOfMemoryError();
        newCapacity = Integer.MAX_VALUE;
    }
    value = Arrays.copyOf(value, newCapacity);
}

擴展的邏輯是,分配一個足夠長度的新數組,然後將原內容拷貝到這個新數組中,最後讓內部的字符數組指向這個新數組,這個邏輯主要靠下面這句代碼實現:

value = Arrays.copyOf(value, newCapacity);

下節我們討論Arrays類,本節就不介紹了,我們主要看下newCapacity是怎麼算出來的。

參數minimumCapacity表示需要的最小長度,需要多少分配多少不就行了嗎?不行,因為那就跟String一樣了,每append一次,都會進行一次內存分配,效率低下。這裡的擴展策略,是跟當前長度相關的,當前長度乘以2,再加上2,如果這個長度不夠最小需要的長度,才用minimumCapacity。

比如說,默認長度為16,長度不夠時,會先擴展到16*2+2即34,然後擴展到34*2+2即70,然後是70*2+2即142,這是一種指數擴展策略。為什麼要加2?大概是因為在原長度為0時也可以一樣工作吧。

為什麼要這麼擴展呢?這是一種折中策略,一方面要減少內存分配的次數,另一方面也要避免空間浪費。在不知道最終需要多長的情況下,指數擴展是一種常見的策略,廣泛應用於各種內存分配相關的計算機程序中。

那如果預先就知道大概需要多長呢?可以調用StringBuilder的另外一個構造方法:

public StringBuilder(int capacity)

toString實現

字符串構建完後,我們來看toString代碼:

public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

基於內部數組新建了一個String,注意,這個String構造方法不會直接用value數組,而會新建一個,以保證String的不可變性。

更多構造方法和append方法

String還有兩個構造方法,分別接受String和CharSequence參數,它們的代碼分別如下:

public StringBuilder(String str) {
    super(str.length() + 16);
    append(str);
}

public StringBuilder(CharSequence seq) {
    this(seq.length() + 16);
    append(seq);
}

邏輯也很簡單,額外多分配16個字符的空間,然後調用append將參數字符添加進來。

append有多種重載形式,可以接受各種類型的參數,將它們轉換為字符,添加進來,這些重載方法有:

public StringBuilder append(boolean b)
public StringBuilder append(char c)
public StringBuilder append(double d)
public StringBuilder append(float f)
public StringBuilder append(int i)
public StringBuilder append(long lng)
public StringBuilder append(char[] str)
public StringBuilder append(char[] str, int offset, int len)
public StringBuilder append(Object obj)
public StringBuilder append(StringBuffer sb)
public StringBuilder append(CharSequence s)
public StringBuilder append(CharSequence s, int start, int end)

具體實現比較直接,就不贅述了。

還有一個append方法,可以添加一個Code Point:

public StringBuilder appendCodePoint(int codePoint) 

如果codePoint為BMP字符,則添加一個char,否則添加兩個char。如果不清楚Code Point的概念,請參見剖析包裝類 (下)。

其他修改方法

除了append, StringBuilder還有一些其他修改方法,我們來看下。

插入

public StringBuilder insert(int offset, String str)

在指定索引offset處插入字符串str,原來的字符後移,offset為0表示在開頭插,為length()表示在結尾插,比如說:

StringBuilder sb = new StringBuilder();
sb.append("老馬說編程");
sb.insert(0, "關注");
sb.insert(sb.length(), "老馬和你一起探索編程本質");
sb.insert(7, ",");
System.out.println(sb.toString());

輸出為

關注老馬說編程,老馬和你一起探索編程本質

來看下insert的實現代碼:

public AbstractStringBuilder insert(int offset, String str) {
    if ((offset < 0) || (offset > length()))
        throw new StringIndexOutOfBoundsException(offset);
    if (str == null)
        str = "null";
    int len = str.length();
    ensureCapacityInternal(count + len);
    System.arraycopy(value, offset, value, offset + len, count - offset);
    str.getChars(value, offset);
    count += len;
    return this;
}

這個實現思路是,在確保有足夠長度後,首先將原數組中offset開始的內容向後挪動n個位置,n為待插入字符串的長度,然後將待插入字符串拷貝進offset位置。

挪動位置調用了System.arraycopy方法,這是個比較常用的方法,它的聲明如下:

public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

將數組src中srcPos開始的length個元素拷貝到數組dest中destPos處。這個方法有個優點,即使src和dest是同一個數組,它也可以正確的處理,比如說,看下面代碼:

int[] arr = new int[]{1,2,3,4};
System.arraycopy(arr, 1, arr, 0, 3);
System.out.println(arr[0]+","+arr[1]+","+arr[2]);

這裡,src和dest都是arr,srcPos為1,destPos為0,length為3,表示將第二個元素開始的三個元素移到開頭,所以輸出為:

2,3,4

arraycopy的聲明有個修飾符native,表示它的實現是通過Java本地接口實現的,Java本地接口是Java提供的一種技術,用於在Java中調用非Java語言實現的代碼,實際上,arraycopy是用C++語言實現的。為什麼要用C++語言實現呢?因為這個功能非常常用,而C++的實現效率要遠高於Java。

其他插入方法

與append類似,insert也有很多重載的方法,如下列舉一二

public StringBuilder insert(int offset, double d)
public StringBuilder insert(int offset, Object obj)

刪除

刪除指定范圍內的字符

public StringBuilder delete(int start, int end) 

其實現代碼為:

public AbstractStringBuilder delete(int start, int end) {
    if (start < 0)
        throw new StringIndexOutOfBoundsException(start);
    if (end > count)
        end = count;
    if (start > end)
        throw new StringIndexOutOfBoundsException();
    int len = end - start;
    if (len > 0) {
        System.arraycopy(value, start+len, value, start, count-end);
        count -= len;
    }
    return this;
}

也是通過System.arraycopy實現的,System.arraycopy被大量應用於StringBuilder的內部實現中,後文就不再贅述了。

刪除一個字符

public StringBuilder deleteCharAt(int index)

替換

public StringBuilder replace(int start, int end, String str)

StringBuilder sb = new StringBuilder();
sb.append("老馬說編程");
sb.replace(3, 5, "Java");
System.out.println(sb.toString());

程序輸出為:

老馬說Java

替換一個字符

public void setCharAt(int index, char ch)

 翻轉字符串

public StringBuilder reverse()

這個方法不只是簡單的翻轉數組中的char,對於增補字符,簡單翻轉後字符就無效了,這個方法能保證其字符依然有效,這是通過單獨檢查增補字符,進行二次翻轉實現的。比如說:

StringBuilder sb = new StringBuilder();
sb.append("a");
sb.appendCodePoint(0x2F81A);//增補字符:

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved