目錄
一.為什么要使用StringBuilder和StringBuffer
字符串的不可變性
性能損耗
二.StringBuilder和StringBuffer
StringBuffer源碼講解
使用方式
三.常用方法總結
示例:?
四.StringBuilder和StringBuffer的區別
一.為什么要使用StringBuilder和StringBuffer
在引入StringBuilder和StringBuffer之前,我們可以回顧一下之前我們對于字符串的拼接操作,大多都是如下直接進行拼接:
public static void main(String[] args) {String s = "hello";s += " world";System.out.println(s); // 輸出:hello world}
?這樣的操作固然是沒有問題的,但是如果要說到效率的話,這樣的代碼效率就非常的低下了,為什么低下呢?說到這里我們就要提到字符串的相關性質了。
字符串的不可變性
String類在設計的時候就是不可改變的,我們可以在JDK1.8的源碼中看見如下的注釋
因此,我們平常使用的對于String字符串操作的方法,都是新建了一個對象來進行操作,想驗證這個結論也很簡單,我們隨便選擇一個方法,我們使用?“ == ” 相當于比較的是倆邊變量的地址的哈希值,我們將一個字符串和對它進行大寫轉換后的字符串進行對比
public static void main(String[] args) {String s = "hello";//s.toUpperCase(Locale.of(s));System.out.println( s == s.toUpperCase(Locale.of(s)));}
?輸出結果:
性能損耗
我們再回顧剛才對于字符串的拼接操作,每一次拼接都要新建一個對象的,?當拼接次數非常多的時候,會造成非常嚴重的性能問題,我們當然也可以驗證這個性能問題,使用?currentTimeMillis 方法可以直接拿到當前時刻系統的時間戳,我們可以通過一個循環來展示一下使用傳統方式拼接字符串的方式會有怎么樣的一個性能損耗
public static void main(String[] args) {long start = System.currentTimeMillis();String s = " ";for(int i = 0; i < 10000; ++i){s += i;}long end = System.currentTimeMillis();System.out.println(end - start);}
輸出結果:
當然這還只是10000次循環就造成了82毫秒的運行時間,實際工程中所需的循環次數往往是不可估摸的,因此使用這種方式進行拼接往往是不能完成我們的性能要求的
二.StringBuilder和StringBuffer
為了解決上述的問題,我們就可以使用StringBuilder和StringBuffer來進行字符串的拼接等操作,我們可以打開API來查看什么是StringBuilder和StringBuffer
StringBuilder:
?StringBuffer:
StringBuffer源碼講解
在一般使用的時候,他們的功能大致相同,這里筆者進行講解就只選取其中一種,整體的包含的方法,使用的技巧大多都是一樣的,因此不用擔心知識覆蓋面不全面,筆者這里就以?StringBuffer 來舉例,我們可以在IDEA中打開 StringBuffer 的源碼,我們可以發現它也是被 final 修飾,繼承了父類 AbstractStringBuilder 并且實現了部分接口
父類?AbstractStringBuilder 中一共倆個成員變量:
我們可以看見它的構造方法包含了不同初始化對應的操作:
使用方式
通過源碼中的的super關鍵字結合和上述父類中的成員變量,我們可以得到以下結論:我們默認新建一個?StringBuffer?的時候實際上是新建了一個16字節的數組,我們也可以使用其他的倆個構造方法在傳參的時候直接傳入大小參數或者直接傳入一個字符串
我們總結三種常用的初始化方式如下:
- 不傳參數,默認16字節大小的數組
- 傳入參數直接申明大小
- 傳入字符串
StringBuffer stringBuffer1 = new StringBuffer();StringBuffer stringBuffer3 = new StringBuffer(20);StringBuffer stringBuffer2 = new StringBuffer("hello");
三.常用方法總結
我們的StringBuilder和StringBuffer最大的特征就是他們內部是可變的,我們通過這倆個類去操作字符串的時候,可以不用新建一個對象,因此我們在進行字符串的拼接的時候往往都是用的這倆個類進行操作,這極大程度上有利于我們提高程運行的效率
我們再談文章開始說的那個例子,我們使用StringBuffer中的 append 方法可以直接拼接字符,我們分別使用傳統的拼接字符和這里的StringBuffer來對比拼接字符所需要的時間
public static void main(String[] args) {long start = System.currentTimeMillis();String s = "";for(int i = 0; i < 10000; ++i){s += i;}long end = System.currentTimeMillis();System.out.println(end - start);System.out.println("=======分割行========");start = System.currentTimeMillis();StringBuffer sbf = new StringBuffer("");for(int i = 0; i < 10000; ++i){sbf.append(i);}end = System.currentTimeMillis();System.out.println(end - start);}
輸出結果:
我們可以直觀的發現:使用?StringBuffer?來拼接字符比直接拼接的效率提高了幾十倍,而如果加多循環次數的話,這個倍數還能繼續再增加,將原本程序的效率提高幾百倍不是夢
除了上述的appen方法,我們將常用的方法總結如下:
方法 | 說明 |
StringBuff append(String str) | 在尾部追加,相當于 String 的 += ,可以追加: boolean 、 char 、 char[] 、 double、 float 、 int 、 long 、 Object 、 String 、 StringBuff 的變量 |
char charAt(int index) | 獲取 index 位置的字符 |
int length() | 獲取字符串的長度 |
int capacity() | 獲取底層保存字符串空間總的大小 |
void ensureCapacity(int mininmumCapacity) | 擴容 |
void setCharAt(int index, char ch) | 將 index 位置的字符設置為 ch |
int indexOf(String str) | 返回 str 第一次出現的位置 |
int indexOf(String str, int fromIndex) | 從 fromIndex 位置開始查找 str 第一次出現的位置 |
int lastIndexOf(String str) | 返回最后一次出現 str 的位置 |
int lastIndexOf(String str, int fromIndex) | 從 fromIndex 位置開始找 str 最后一次出現的位置 |
StringBuff insert(int offset, String str) | 在 offset 位置插入:八種基類類型 & String 類型 & Object 類型數據 |
StringBuffer deleteCharAt(int index) | 刪除 index 位置字符 |
StringBuffer delete(int start, int end) | 刪除 [start, end) 區間內的字符 |
StringBuffer replace(int start, int end, String str) | 將 [start, end) 位置的字符替換為 str |
String substring(int start) | 從 start 開始一直到末尾的字符以 String 的方式返回 |
String substring(int start, int end) | 將 [start, end) 范圍內的字符以 String 的方式返回 |
StringBuffer reverse() | 反轉字符串 |
String toString() | 將所有字符按照 String 的方式返回 |
示例:?
public static void main(String[] args) {StringBuilder sb1 = new StringBuilder("hello");StringBuilder sb2 = sb1;// 追加:即尾插-->字符、字符串、整形數字sb1.append(' '); // hellosb1.append("world"); // hello worldsb1.append(123); // hello world123System.out.println(sb1); // hello world123System.out.println(sb1 == sb2); // trueSystem.out.println(sb1.charAt(0)); // 獲取0號位上的字符 hSystem.out.println(sb1.length()); // 獲取字符串的有效長度14System.out.println(sb1.capacity()); // 獲取底層數組的總大小sb1.setCharAt(0, 'H'); // 設置任意位置的字符 Hello world123sb1.insert(0, "Hello world!!!"); // Hello world!!!Hello world123System.out.println(sb1);System.out.println(sb1.indexOf("Hello")); // 獲取Hello第一次出現的位置System.out.println(sb1.lastIndexOf("hello")); // 獲取hello最后一次出現的位置sb1.deleteCharAt(0); // 刪除首字符sb1.delete(0, 5); // 刪除[0, 5)范圍內的字符String str = sb1.substring(0, 5); // 截取[0, 5)區間中的字符以String的方式返回System.out.println(str);sb1.reverse(); // 字符串逆轉str = sb1.toString(); // 將StringBuffer以String的方式返回System.out.println(str);}
從上述例子可以看出:String和StringBuilder最大的區別在于String的內容無法修改,而StringBuilder的內容可以修改,因此頻繁修改字符串的情況考慮使用StringBuilder
注意:String和StringBuilder類不能直接轉換。如果要想互相轉換,可以采用如下原則:
- String變為StringBuilder: 利用StringBuilder的構造方法或append()方法
- StringBuilder變為String: 調用toString()方法
四.StringBuilder和StringBuffer的區別
我們可以打開StringBuffer的源碼,我們觀察到幾乎每一個StringBuffer的前面都有一個synchronized來修飾StringBuffer,這里的synchronized其實就可以理解為一個鎖,被synchronized修飾的方法不允許同時被多個對象在同一時刻調用,這樣的設立是為了多線程的程序的安全性。
舉個通俗的例子:現在有小王,小李,小紅三個人想上廁所,但是廁所只有一個,小王先去上廁所,那么小李或者小紅就只能等小王用完廁所出來了后,才能去上廁所
而我們的StringBuffer就是類似這樣設置的,當一個對象調用被synchronized修飾的方法的時候,這個方法就會被上鎖,其他對象不能使用,只有當前這個對象使用完這個方法之后,也就是解鎖之后,其他對象才能訪問
當我們打開StringBuilder的源碼會發現我們的StringBuilder并沒有這樣的設置操作
總結:
也就是說StringBuffer是為了多線程的安全,但是頻繁的上鎖解鎖會降低代碼的運行效率,而StringBuilder雖然沒有安全性的考慮,但是它不用開鎖解鎖,所以運行效率更高,我們在編程中如果需要安全性就使用StringBuffer,如果是為了高效率就使用StringBuilder
?本次的分享就到此為止了,希望我的分享能給您帶來幫助,也歡迎大家三連支持,你們的點贊就是博主更新最大的動力!
如有不同意見,歡迎評論區積極討論交流,讓我們一起學習進步!
有相關問題也可以私信博主,評論區和私信都會認真查看的,我們下次再見