一文講解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í)的開發(fā)人員考慮到多個(gè)線程對(duì)一個(gè)字符串的修改可能出現(xiàn)線程不安全的問題,于是讓 StringBuffer 在擁有可修改字符串的功能的情況下,又給它加上了線程安全的機(jī)制??吹竭@里是不是覺得還挺好,挺正常的?但是要知道一個(gè)前提,那就是在 Java5 之前的 Java 在處理字符串的速度上一直被別人詬病,原因出在哪里?原因就在于這個(gè) StringBuffer 上面。
被人詬病的 StringBuffer
StringBuffer 本來是為了實(shí)現(xiàn)大量修改字符串的功能而出現(xiàn)的,但卻因?yàn)?Java 的開發(fā)人員給它加了個(gè)線程安全的功能,導(dǎo)致它執(zhí)行效率極大地下降。這個(gè)線程安全的功能的實(shí)現(xiàn)并不是像我們現(xiàn)在用的方法,當(dāng)時(shí)只是保證沒有異常拋出,程序可以正常運(yùn)行下去而已。在 Java 中,要實(shí)現(xiàn)字符串的相加,用加法運(yùn)算符將兩個(gè)字符串相加即可。但在這個(gè)過程中,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í)行效率,以至于后來被廣大程序員詬病。
StringBuilder 的出現(xiàn)
我們仔細(xì)地想一下,實(shí)際上也并沒有多少地方需要在修改字符串的同時(shí)保證線程安全,就算有,我們給它加個(gè)鎖就行?;谶@種想法,在 StringBuffer 出現(xiàn) 10 年之后,Java 的開發(fā)人員回過頭看這個(gè)問題,才發(fā)現(xiàn) StringBuffer 的實(shí)現(xiàn)是多么的愚蠢,于是后來在 Java5 就有了 StringBuilder。StringBuilder 同樣可以快速高效地修改字符串,同時(shí)不是線程安全的。雖然它不是線程安全的,但是它的執(zhí)行效率卻比 StringBuffer 要高上了不少。在 Java5 之后的版本中,字符串相加隱含的轉(zhuǎn)化過程中,不再將 String 轉(zhuǎn)化為 StringBuffer,而是轉(zhuǎn)化成 StringBuilder。
歷史故事講完了,下面正式開始講解它們各自的用法和特性。
String 類
String 類是 Java 用來存儲(chǔ)字符串的內(nèi)置類,在 String 初始化之后,其值就不可改變。
創(chuàng)建字符串
創(chuàng)建字符串的方式有兩種,直接創(chuàng)建和使用 new 關(guān)鍵字來創(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 + "" 這一過程中,編譯器會(huì)隱含地將 str2 轉(zhuǎn)換成 StringBuilder(Java5 之前為 StringBuffer),然后再與 "" 相加,此過程會(huì)產(chǎn)生一個(gè)新的 StringBuilder 類,就是轉(zhuǎn)換而來的那個(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");
通過 new 關(guān)鍵字創(chuàng)建的 String 和其他一般的類的創(chuàng)建一樣,數(shù)據(jù)是存儲(chǔ)在 String 類的對(duì)象的堆上,即通過 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ū)別
通過 new 關(guān)鍵字創(chuàng)建的 String 類會(huì)調(diào)用 String 類的構(gòu)造方法,String 類的構(gòu)造方法有 11 種,除了通過字符串來創(chuàng)建 String 類之外,我們還可以用字符數(shù)組等方式來創(chuàng)建。
char[] s = {'J', 'a', 'v', 'a'}; String str = new String(s);
格式化字符串
在 Java 中,格式化字符串的方法有很多,比如有 C 語言風(fēng)格的 printf,也有 Java 風(fēng)格的 String.format。
System.out.printf("%f, %.2f, %s", Math.E, Math.PI, "Java");
雖然說是 C 語言風(fēng)格,但還是略微有一點(diǎn)點(diǎn)不同,比如說,用 %d 來輸出浮點(diǎn)數(shù)在 C 語言里面編譯是不會(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 開始,因?yàn)?0 是格式化字符串;
[flag] 表示格式化的標(biāo)識(shí),有以下幾種:
- - :左對(duì)齊(默認(rèn)是右對(duì)齊的),和長(zhǎng)度限定一起使用;
- + :正數(shù)前加正號(hào);
- 0 :不夠長(zhǎng)度用 0 來補(bǔ)齊;
- # :對(duì)非十進(jìn)制的數(shù)前加上標(biāo)識(shí);
- , :對(duì)于十進(jìn)制整數(shù)每隔三位加一個(gè)逗號(hào)分隔符;
- ( :若為負(fù)數(shù),則用括號(hào)將其括起來;
- (空格字符):正數(shù)前空一格,負(fù)數(shù)沒有變化;
- 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 和其他語言不同的地方(方便 C/C++ 和 Python 的人熟悉 Java):
- C/C++ 的整數(shù)可以用單引號(hào)進(jìn)行分隔,但是 Java 和 Python 不可以;
- Java 可以通過逗號(hào)格式字符串來達(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.142
Java:
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,890
Python:(下面的類型提示語法是為了讓讀者更容易理解)
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.142
String 類的各種方法
常用方法
方法名 | 方法描述 |
int length() | 返回字符串的長(zhǎng)度 |
char charAt(int index) | 返回字符串中索引為 index 處的字符 |
int indexOf(char c) | 返回第一個(gè)字符 c 的索引,若沒有則返回 -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 開頭,并返回測(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í)沒有,因?yàn)?Java 中的變量都只是對(duì)象的引用。原來值為 "Java" 的 String 類型對(duì)象實(shí)際上還存在于內(nèi)存中,str = "java"; 只不過是將變量 str 的引用改到了常量 "java" 上面去了。
?String 類型不可變
實(shí)際上,String 就是一個(gè) char 類型的數(shù)組,且 String 封裝的這個(gè) char 數(shù)組是用 final 關(guān)鍵字修飾的,String 本身也被 final 修飾,因此無法被改變。所以 String 類型的方法只能返回一個(gè)修改原 String 的、新的 String 類型的變量,而無法對(duì)原值進(jìn)行修改。要產(chǎn)生一個(gè)新的 String 必然要開辟新的內(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ì)指出來的。
下面是 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)存的消耗誰更多顯而易見。
concat 方法與加法運(yùn)算符
String.concat 方法和使用加號(hào)來連接字符串的結(jié)果都是得到連接后的字符串,但是這兩者有什么區(qū)別嗎?
String str1 = "Ja" + "va"; String str2 = "Ja".concat("va"); System.out.println(str1 == str2); // Output: false
無疑,str1 和 str2 的值是相等,但是兩者的引用地址不同,從前面的知識(shí)我們可以知道,str1 的內(nèi)存地址在公共的常量池中,上面的代碼就說明 str2 的內(nèi)存地址在 String 類的堆上。concat 方法實(shí)際上先復(fù)制了原來的字符串后再與新的字符串拼接,然后返回了一個(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)行拼接。也就是說,加法拼接字符串的底層實(shí)際是調(diào)用 append 方法。
適用場(chǎng)景
String、StringBuffer 和 StringBuilder 用處不同,各有各的適用場(chǎng)景??偨Y(jié)如下:
類名稱 | 是否可修改 | 線程是否安全 | 相對(duì)速度比較 |
String | 否 | \ | 緩慢 |
StringBuffer | 是 | 是 | 一般 |
StringBuilder | 是 | 否 | 快速 |
綜上所述,當(dāng)我們不需要對(duì)字符串做太多修改的時(shí)候,我們就選擇 String 類,當(dāng)我們需要對(duì)字符串進(jìn)行大量且頻繁的修改時(shí),我們就選擇 StringBuilder 類,除非遇到了需要線程安全的情況。不過,就算遇到了需要線程安全的情況,仍然推薦使用 StringBuilder,因?yàn)?StringBuffer 的線程安全,僅僅是保證 Jvm 不拋出異常,順利地往下執(zhí)行而已,它并不能保證邏輯正確和調(diào)用順序正確。在大多數(shù)時(shí)候,我們需要的是鎖。但也可以偷懶使用 StringBuffer。
從 Java5 之后,用加號(hào)來連接字符串的時(shí)候,都會(huì)隱含地調(diào)用 StringBuilder 類,因此,大部分情況下用加號(hào)連接字符串的操作已經(jīng)沒有太多的性能損失,但并非絕對(duì)的。如果在有循環(huán)的情況下,編譯器可能無法完全做到智能地替換,這個(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)文章
解決使用redisTemplate高并發(fā)下連接池滿的問題
這篇文章主要介紹了解決使用redisTemplate高并發(fā)下連接池滿的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12微信公眾號(hào)開發(fā)之設(shè)置自定義菜單實(shí)例代碼【java版】
這篇文章主要介紹了微信公眾號(hào)開發(fā)之設(shè)置自定義菜單實(shí)例代碼,本實(shí)例是為了實(shí)現(xiàn)在管理后臺(tái)實(shí)現(xiàn)微信菜單的添加刪除管理。需要的朋友可以參考下2018-06-06深入理解Java定時(shí)調(diào)度(Timer)機(jī)制
這篇文章主要介紹了深入理解Java定時(shí)調(diào)度(Timer)機(jī)制,本節(jié)我們主要分析 Timer 的功能。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01spring batch使用reader讀數(shù)據(jù)的內(nèi)存容量問題詳解
這篇文章主要介紹了spring batch使用reader讀數(shù)據(jù)的內(nèi)存容量問題詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07java實(shí)現(xiàn)excel自定義樣式與字段導(dǎo)出詳細(xì)圖文教程
最近接到一個(gè)需求,客戶不滿意原本導(dǎo)出的csv文件,想要導(dǎo)出Excel文件,下面這篇文章主要給大家介紹了關(guān)于java實(shí)現(xiàn)excel自定義樣式與字段導(dǎo)出詳細(xì)圖文教程2023-09-09在Java的Spring框架的程序中使用JDBC API操作數(shù)據(jù)庫
這篇文章主要介紹了在Java的Spring框架的程序中使用JDBC API操作數(shù)據(jù)庫的方法,并通過示例展示了其存儲(chǔ)過程以及基本SQL語句的應(yīng)用,需要的朋友可以參考下2015-12-12