一文講解Java的String、StringBuffer和StringBuilder的使用與區(qū)別
提到 String、StringBuffer 和 StringBuilder,就不得不談及它們的歷史,在了解它們的歷史之后,我們對(duì)它們的理解將更上一級(jí)臺(tái)階!
發(fā)展歷史
String 與 StringBuffer 的出現(xiàn)
String 和 StringBuffer 在 Java1.0 中就已經(jīng)有了,目前也一直存在于各個(gè) Java 版本之中,但是 StringBuilder 是在 Java5 中才被引入。我們都知道,String 是 Java 中的字符串類,是不可修改的,在 Java1.0 的時(shí)候,若要對(duì)字符串進(jìn)行大量修改,應(yīng)當(dāng)使用 StringBuffer,它是可修改的,同時(shí),當(dāng)時(shí)的開(kāi)發(fā)人員考慮到多個(gè)線程對(duì)一個(gè)字符串的修改可能出現(xiàn)線程不安全的問(wèn)題,于是讓 StringBuffer 在擁有可修改字符串的功能的情況下,又給它加上了線程安全的機(jī)制??吹竭@里是不是覺(jué)得還挺好,挺正常的?但是要知道一個(gè)前提,那就是在 Java5 之前的 Java 在處理字符串的速度上一直被別人詬病,原因出在哪里?原因就在于這個(gè) StringBuffer 上面。
被人詬病的 StringBuffer
StringBuffer 本來(lái)是為了實(shí)現(xiàn)大量修改字符串的功能而出現(xiàn)的,但卻因?yàn)?Java 的開(kāi)發(fā)人員給它加了個(gè)線程安全的功能,導(dǎo)致它執(zhí)行效率極大地下降。這個(gè)線程安全的功能的實(shí)現(xiàn)并不是像我們現(xiàn)在用的方法,當(dāng)時(shí)只是保證沒(méi)有異常拋出,程序可以正常運(yùn)行下去而已。在 Java 中,要實(shí)現(xiàn)字符串的相加,用加法運(yùn)算符將兩個(gè)字符串相加即可。但在這個(gè)過(guò)程中,Java5 之前是有 String 自動(dòng)隱含地轉(zhuǎn)換成 StringBuffer,再進(jìn)行操作這一個(gè)步驟的(畢竟 String 類不可直接修改)。只要有這些步驟,就可以實(shí)現(xiàn)字符串的修改,但是呢,StringBuffer 有個(gè)線程安全的功能,它會(huì)在上面提到的步驟中還額外的執(zhí)行一些功能,以保證線程的安全,而且,這里實(shí)現(xiàn)線程安全的方式和我們現(xiàn)在用鎖的方式是不一樣的!它這里的實(shí)現(xiàn)線程安全的方式極為繁瑣且復(fù)雜,這就大大降低了 StringBuffer 的執(zhí)行效率,以至于后來(lái)被廣大程序員詬病。
StringBuilder 的出現(xiàn)
我們仔細(xì)地想一下,實(shí)際上也并沒(méi)有多少地方需要在修改字符串的同時(shí)保證線程安全,就算有,我們給它加個(gè)鎖就行。基于這種想法,在 StringBuffer 出現(xiàn) 10 年之后,Java 的開(kāi)發(fā)人員回過(guò)頭看這個(gè)問(wèn)題,才發(fā)現(xiàn) StringBuffer 的實(shí)現(xiàn)是多么的愚蠢,于是后來(lái)在 Java5 就有了 StringBuilder。StringBuilder 同樣可以快速高效地修改字符串,同時(shí)不是線程安全的。雖然它不是線程安全的,但是它的執(zhí)行效率卻比 StringBuffer 要高上了不少。在 Java5 之后的版本中,字符串相加隱含的轉(zhuǎn)化過(guò)程中,不再將 String 轉(zhuǎn)化為 StringBuffer,而是轉(zhuǎn)化成 StringBuilder。
歷史故事講完了,下面正式開(kāi)始講解它們各自的用法和特性。
String 類
String 類是 Java 用來(lái)存儲(chǔ)字符串的內(nèi)置類,在 String 初始化之后,其值就不可改變。
創(chuàng)建字符串
創(chuàng)建字符串的方式有兩種,直接創(chuàng)建和使用 new 關(guān)鍵字來(lái)創(chuàng)建
直接創(chuàng)建
String str = "Java";
直接創(chuàng)建的 String 類的數(shù)據(jù)存儲(chǔ)在公共的常量池中(Java 的常量?jī)?yōu)化機(jī)制),即直接創(chuàng)建的相同值的不同 String 類的引用相同。
String str1 = "Java"; String str2 = "J" + "a" + "v" + "a"; System.out.println(str1 == str2); // Output: true
但下面這種情況要注意一下,它和上面的不同:
String str1 = "Java"; String str2 = "Java"; String str3 = str2 + ""; System.out.println(str1 == str2); // Output: true System.out.println(str2 == str3); // Output: false
為什么 str2 與 str3 不相等呢?實(shí)際上 str2 + "" 這一過(guò)程中,編譯器會(huì)隱含地將 str2 轉(zhuǎn)換成 StringBuilder(Java5 之前為 StringBuffer),然后再與 "" 相加,此過(guò)程會(huì)產(chǎn)生一個(gè)新的 StringBuilder 類,就是轉(zhuǎn)換而來(lái)的那個(gè)。這個(gè)新產(chǎn)生的 StringBuilder 再轉(zhuǎn)換為 String 類并賦值給變量 str3,因此,str3 的引用位置是 String 對(duì)象的堆上(與 new 關(guān)鍵字創(chuàng)建的 String 類相同),故與 str2 不相等。
new 關(guān)鍵字創(chuàng)建
String str = new String("Java");通過(guò) new 關(guān)鍵字創(chuàng)建的 String 和其他一般的類的創(chuàng)建一樣,數(shù)據(jù)是存儲(chǔ)在 String 類的對(duì)象的堆上,即通過(guò) new 關(guān)鍵字創(chuàng)建的相同值的不同 String 類的引用不同。
String str1 = new String("Java");
String str2 = new String("Java");
System.out.println(str1 == str2); // Output: false
?String 創(chuàng)建方式的區(qū)別
通過(guò) new 關(guān)鍵字創(chuàng)建的 String 類會(huì)調(diào)用 String 類的構(gòu)造方法,String 類的構(gòu)造方法有 11 種,除了通過(guò)字符串來(lái)創(chuàng)建 String 類之外,我們還可以用字符數(shù)組等方式來(lái)創(chuàng)建。
char[] s = {'J', 'a', 'v', 'a'};
String str = new String(s);格式化字符串
在 Java 中,格式化字符串的方法有很多,比如有 C 語(yǔ)言風(fēng)格的 printf,也有 Java 風(fēng)格的 String.format。
System.out.printf("%f, %.2f, %s", Math.E, Math.PI, "Java");雖然說(shuō)是 C 語(yǔ)言風(fēng)格,但還是略微有一點(diǎn)點(diǎn)不同,比如說(shuō),用 %d 來(lái)輸出浮點(diǎn)數(shù)在 C 語(yǔ)言里面編譯是不會(huì)報(bào)錯(cuò)的,盡管輸出的結(jié)果不對(duì),但是這在 Java 里面是會(huì)報(bào)錯(cuò)的。
用 String 類的 format 方法也能格式化字符串,它可以看作是強(qiáng)化了的格式化字符串工具。
它既可以像 printf 那樣格式化字符串:
System.out.println(String.format("%f, %.2f, %s", Math.E, Math.PI, "Java"));也有它自己獨(dú)特的方式:%[index]$[flag][type]
[index] 表示參數(shù)的索引,從 1 開(kāi)始,因?yàn)?0 是格式化字符串;
[flag] 表示格式化的標(biāo)識(shí),有以下幾種:
- - :左對(duì)齊(默認(rèn)是右對(duì)齊的),和長(zhǎng)度限定一起使用;
- + :正數(shù)前加正號(hào);
- 0 :不夠長(zhǎng)度用 0 來(lái)補(bǔ)齊;
- # :對(duì)非十進(jìn)制的數(shù)前加上標(biāo)識(shí);
- , :對(duì)于十進(jìn)制整數(shù)每隔三位加一個(gè)逗號(hào)分隔符;
- ( :若為負(fù)數(shù),則用括號(hào)將其括起來(lái);
- (空格字符):正數(shù)前空一格,負(fù)數(shù)沒(méi)有變化;
- m.nf :對(duì)于浮點(diǎn)數(shù),輸出長(zhǎng)度為 m 位,保留小數(shù)點(diǎn)后 n 位;
- e/E :以科學(xué)計(jì)數(shù)法表示浮點(diǎn)數(shù);
- g/G :根據(jù)情況智能選擇以普通形式或者科學(xué)計(jì)數(shù)法輸出浮點(diǎn)數(shù);
- a/A :輸出帶有效位數(shù)和指數(shù)的十六進(jìn)制浮點(diǎn)數(shù);
int i = 1234567890;
System.out.println(String.format("%,d", i)); // Output: 1,234,567,890
System.out.println(String.format("%16d", i)); // Output: 1234567890
System.out.println(String.format("%016d", i)); // Output: 0000001234567890
System.out.println(String.format("%-16d", i)); // Output: 1234567890
System.out.println(String.format("% d", i)); // Output: 1234567890
System.out.println(String.format("% d", -i)); // Output: -1234567890
System.out.println(String.format("%+d", i)); // Output: +1234567890
System.out.println(String.format("%(d", -i)); // Output: (1234567890)
System.out.println(String.format("%#x", i)); // Output: 0x499602d2
System.out.println(String.format("%x", i)); // Output: 499602d2
System.out.println(String.format("%o", i)); // Output: 11145401322
System.out.println(String.format("%b", i)); // Output: true
double d = 3.1415926;
System.out.println(String.format("%12.5f", d)); // Output: 3.14159
System.out.println(String.format("%g", d)); // Output: 3.14159
System.out.println(String.format("%e", d)); // Output: 3.141593e+00
System.out.println(String.format("%a", d)); // Output: 0x1.921fb4d12d84ap1[type] 標(biāo)識(shí)格式化的類型,有 d(整數(shù))、f(浮點(diǎn)數(shù))、s(字符串)、c(字符)、b(布爾值) 等。注意,這里的整數(shù)包括 int 類型和 long 類型,浮點(diǎn)數(shù)包括 float 類型和 double 類型。除了上面的基本類型外,還有幾種特殊的:x(十六進(jìn)制)、o(八進(jìn)制)。
int i = 1;
long l = 1l;
float f = 1.f;
double d = 1.;
char c = 'a';
String str = "Java";
String format = String.format("%6$s %5$c %4$f %3$f %2$d %1$d", i, l, f, d, c, str);
System.out.println(format); // Output: Java a 1.000000 1.000000 1 1關(guān)于 Java 中輸出整數(shù)的二進(jìn)制的方法:
System.out.println(Integer.toBinaryString(123)); // Output: 1111011
輸出二進(jìn)制不能用格式化輸出了,但可以用 Integer 類的 toBinaryString 方法將其轉(zhuǎn)化為二進(jìn)制形式的字符串,然后再輸出即可。
這里再補(bǔ)充一些 Java 和其他語(yǔ)言不同的地方(方便 C/C++ 和 Python 的人熟悉 Java):
- C/C++ 的整數(shù)可以用單引號(hào)進(jìn)行分隔,但是 Java 和 Python 不可以;
- Java 可以通過(guò)逗號(hào)格式字符串來(lái)達(dá)到整數(shù)每隔三位分隔的效果,C/C++ 和 Python 不可以;
- C/C++ 和 Python 可以將參數(shù)作為精度值并進(jìn)行輸出,而 Java 不可以;
- C/C++、Java 和 Python 在十六進(jìn)制、十進(jìn)制、二進(jìn)制上表示方法一樣,但在八進(jìn)制上 Python 與 C/C++ 和 Java 表示方法不同;
C/C++:
int num = 1'234'567'890;
int num = 0xabc; // 十六進(jìn)制
int num = 123; // 十進(jìn)制
int num = 0123; // 八進(jìn)制
int num = 0b101; // 二進(jìn)制
double d = 3.14;
long long l = 112358;
printf("%lf, %lld", d, f); // Output: 3.14, 112358
printf("%*.*f", 6, 3, 3.1415926); // Output: 3.142Java:
int num1 = 0xabc; // 十六進(jìn)制
int num2 = 123; // 十進(jìn)制
int num3 = 0123; // 八進(jìn)制
int num4 = 0b101; // 二進(jìn)制
int num = 1234567890;
System.out.println(String.format("%,d", num)); // Output: 1,234,567,890Python:(下面的類型提示語(yǔ)法是為了讓讀者更容易理解)
num: int = 0xabc # 十六進(jìn)制
num: int = 123 # 十進(jìn)制
num: int = 0o123 # 八進(jìn)制
num: int = 0b101 # 二進(jìn)制
print('%*.*f' % (6, 3, 3.1415926)) # Output: 3.142String 類的各種方法
常用方法
| 方法名 | 方法描述 |
| int length() | 返回字符串的長(zhǎng)度 |
| char charAt(int index) | 返回字符串中索引為 index 處的字符 |
| int indexOf(char c) | 返回第一個(gè)字符 c 的索引,若沒(méi)有則返回 -1 |
| String concat(String str) | 返回原字符串和 str 連接后的新字符串 |
| String[] split(char c) | 返回以字符 c 進(jìn)行分割得到的字符串?dāng)?shù)組 |
| boolean equals(Object anObject) | 將字符串與 anObject 進(jìn)行比較,并返回比較結(jié)果 |
| boolean startsWith(String str) | 測(cè)試字符串是否以 str 開(kāi)頭,并返回測(cè)試結(jié)果 |
| boolean endsWith(String str) | 測(cè)試字符串是否以 str 結(jié)尾,并返回測(cè)試結(jié)果 |
| boolean contains(String str) | 判斷字符串中是否包含 str,并返回判斷結(jié)果 |
| String substring(int start, int end) | 截取字符串,返回索引為 start 和索引為 end(不含)之間的字符串 |
String format(String fmt, Object... args) | 返回格式化后的字符串 |
String 與 char
眾所周知,String 的值是不可改變的,但是下面的代碼又是為什么呢?
String str = "Java"; System.out.println(str); // Output: Java str = "java"; System.out.println(str); // Output: java
上面的代碼表面看上去 String 類型的變量 str 的值被改變,其實(shí)沒(méi)有,因?yàn)?Java 中的變量都只是對(duì)象的引用。原來(lái)值為 "Java" 的 String 類型對(duì)象實(shí)際上還存在于內(nèi)存中,str = "java"; 只不過(guò)是將變量 str 的引用改到了常量 "java" 上面去了。

?String 類型不可變
實(shí)際上,String 就是一個(gè) char 類型的數(shù)組,且 String 封裝的這個(gè) char 數(shù)組是用 final 關(guān)鍵字修飾的,String 本身也被 final 修飾,因此無(wú)法被改變。所以 String 類型的方法只能返回一個(gè)修改原 String 的、新的 String 類型的變量,而無(wú)法對(duì)原值進(jìn)行修改。要產(chǎn)生一個(gè)新的 String 必然要開(kāi)辟新的內(nèi)存,這將花費(fèi)不少的時(shí)間,因此只有在對(duì) String 有少量修改的需求情況下,才使用 String 類,若要大量修改,那么還是需要 StringBuffer 類和 StringBuilder 類。
StringBuffer 與 StringBuilder
StringBuffer 和 StringBuilder 與 String 最大的不同之處在于,它們可以大量且頻繁地修改字符串的值而不產(chǎn)生新的字符串。StringBuffer 和 StringBuilder 最大的不同之處在于,StringBuffer 是線程安全的,而 StringBuilder 不是線程安全的,但 StringBuilder 相較于 StringBuffer 有速度優(yōu)勢(shì),絕大多數(shù)情況下,推薦使用 StringBuilder。
繼承結(jié)構(gòu)
String 是直接繼承自 CharSequence,而 StringBuffer 和 StringBuilder 繼承自 AbstractStringBuilder,AbstractStringBuilder 又同時(shí)繼承自 CharSequence 和 Appendable。

繼承結(jié)構(gòu)
基本用法
這里以 StringBuilder 為例,介紹它的基本用法,StringBuffer 的用法與之類似,不同的地方會(huì)指出來(lái)的。
下面是 StringBuilder 的常用方法:
| 方法名 | 方法描述 |
| append(String str) | 在字符串的后面添加字符串 str |
| insert(int start, String str) | 在索引為 start 的位置插入字符串 str |
| delete(int start, int end) | 將索引為 start 和索引為 end 之間的字符串刪除 |
| replace(int start, int end, String str) | 將索引為 start 和索引為 end 之間的字符串替換為字符串 str |
| reverse() | 反轉(zhuǎn)字符串本身 |
StringBuilder sb = new StringBuilder(10);
sb.append("Java"); // sb: Java
sb.insert(1, "java"); // sb: Jjavaava
sb.delete(2, 3); // sb: Jjvaava
sb.replace(4, 5, "JAVA"); // sb: JjvaJAVAva
sb.reverse(); // sb: avAVAJavjJ
?StringBuilder 方法示意圖
與 String 的區(qū)別
在調(diào)用 String 類的 concat 方法(字符串相加)時(shí),實(shí)際上是將兩個(gè)字符串相加并得到一個(gè)新的 String 類并返回,而并非對(duì)原 String 進(jìn)行修改,而 StringBuffer 和 StringBuilder 是對(duì)自身的值直接進(jìn)行修改的,速度和內(nèi)存的消耗誰(shuí)更多顯而易見(jiàn)。
concat 方法與加法運(yùn)算符
String.concat 方法和使用加號(hào)來(lái)連接字符串的結(jié)果都是得到連接后的字符串,但是這兩者有什么區(qū)別嗎?
String str1 = "Ja" + "va";
String str2 = "Ja".concat("va");
System.out.println(str1 == str2); // Output: false無(wú)疑,str1 和 str2 的值是相等,但是兩者的引用地址不同,從前面的知識(shí)我們可以知道,str1 的內(nèi)存地址在公共的常量池中,上面的代碼就說(shuō)明 str2 的內(nèi)存地址在 String 類的堆上。concat 方法實(shí)際上先復(fù)制了原來(lái)的字符串后再與新的字符串拼接,然后返回了一個(gè)新的字符串對(duì)象,所以地址不在公共常量池中,與 new 創(chuàng)建的字符串類似。
除 concat 和加法運(yùn)算符之外,還有一種方式可以實(shí)現(xiàn)字符串的拼接,那就是 StringBuffer 類或 StringBuilder 類的 append 方法,實(shí)際上,加法運(yùn)算符拼接字符串,轉(zhuǎn)換成 StringBuffer 或 StringBuilder 后再進(jìn)行字符串拼接的操作就是使用 append 方法進(jìn)行拼接。也就是說(shuō),加法拼接字符串的底層實(shí)際是調(diào)用 append 方法。
適用場(chǎng)景
String、StringBuffer 和 StringBuilder 用處不同,各有各的適用場(chǎng)景。總結(jié)如下:
| 類名稱 | 是否可修改 | 線程是否安全 | 相對(duì)速度比較 |
| String | 否 | \ | 緩慢 |
| StringBuffer | 是 | 是 | 一般 |
| StringBuilder | 是 | 否 | 快速 |
綜上所述,當(dāng)我們不需要對(duì)字符串做太多修改的時(shí)候,我們就選擇 String 類,當(dāng)我們需要對(duì)字符串進(jìn)行大量且頻繁的修改時(shí),我們就選擇 StringBuilder 類,除非遇到了需要線程安全的情況。不過(guò),就算遇到了需要線程安全的情況,仍然推薦使用 StringBuilder,因?yàn)?StringBuffer 的線程安全,僅僅是保證 Jvm 不拋出異常,順利地往下執(zhí)行而已,它并不能保證邏輯正確和調(diào)用順序正確。在大多數(shù)時(shí)候,我們需要的是鎖。但也可以偷懶使用 StringBuffer。
從 Java5 之后,用加號(hào)來(lái)連接字符串的時(shí)候,都會(huì)隱含地調(diào)用 StringBuilder 類,因此,大部分情況下用加號(hào)連接字符串的操作已經(jīng)沒(méi)有太多的性能損失,但并非絕對(duì)的。如果在有循環(huán)的情況下,編譯器可能無(wú)法完全做到智能地替換,這個(gè)時(shí)候我們還是自己手動(dòng)使用 StringBuilder 類比較好。
到此這篇關(guān)于一文講解Java的String、StringBuffer和StringBuilder的使用與區(qū)別的文章就介紹到這了,更多相關(guān)Java String StringBuffer StringBuilder內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot實(shí)現(xiàn)加載yml文件中字典數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了SpringBoot如何實(shí)現(xiàn)加載yml文件中字典數(shù)據(jù),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-04-04
Java利用StringBuffer替換特殊字符的方法實(shí)現(xiàn)
這篇文章主要介紹了Java利用StringBuffer替換特殊字符的方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
Java面試之如何實(shí)現(xiàn)10億數(shù)據(jù)判重
當(dāng)數(shù)據(jù)量比較大時(shí),使用常規(guī)的方式來(lái)判重就不行了,所以這篇文章小編主要來(lái)和大家介紹一下Java實(shí)現(xiàn)10億數(shù)據(jù)判重的相關(guān)方法,希望對(duì)大家有所幫助2024-02-02
用Spring將Service注入到Servlet中的流程步驟
在Java Web開(kāi)發(fā)中,??Servlet??是一個(gè)非常重要的組件,它用于處理客戶端的請(qǐng)求并生成響應(yīng),而?Spring??框架則是一個(gè)廣泛使用的依賴注入框架,可以幫助開(kāi)發(fā)者管理應(yīng)用中的對(duì)象及其依賴關(guān)系,本文將介紹如何使用Spring框架將Service層的對(duì)象注入到Servlet中2025-01-01
Mybatis批量插入數(shù)據(jù)返回主鍵的實(shí)現(xiàn)
這篇文章主要介紹了Mybatis批量插入數(shù)據(jù)返回主鍵的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
Java web數(shù)據(jù)可視化實(shí)現(xiàn)原理解析
這篇文章主要介紹了Java web數(shù)據(jù)可視化實(shí)現(xiàn)原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03

