Java源碼之String
說在前面:
為什麼看源碼: 最好的學習的方式就是模仿,接下來才是創造。而源碼就是我們最好的模仿對象,因為寫源碼的人都不是一般的人,所以用心學習源碼,也就可能變成牛逼的人。其次,看源碼,是一項修練內功的重要方式,書看百遍其意自現,源碼也是一樣,前提是你不要懼怕源碼,要用心的看,看不懂了,不要懷疑自己的智商,回過頭來多看幾遍,我就是這樣做的,一遍有一遍的感受,等你那天看源碼不由的驚嘆一聲,這個代碼寫得太好了,恭喜你,你已經離優秀不遠了。最後,看源碼,能培養我們的編程思維,當然這個層次有點高了,需要時間積累,畢竟我離這個境界也有點遠。
今天就來談談java的string的源碼實現,後續我也會寫javaSe的源碼系列,歡迎圍觀和交流。
1.繼承關係
繼承三個接口的說明:
Comparable接口:
實現對象之間比較的接口,它的核心方法只有一個:
public int compareTo(T o);
CharSequence接口:
CharSequence
是char值的可讀序列。該接口提供對許多不同種類的char
序列的統一隻讀訪問。CharSequence
是一個接口,它只包括length(), charAt(int index), subSequence(int start, int end)
這幾個API接口。除了String
實現了CharSequence
之外,StringBuffer
和StringBuilder
也實現了 CharSequence
接口。
那麼String
為什麼實現Charsequence
這個接口呢。這裏就要涉及一個java的重要特性,也就是多態。看下面的代碼
public void charSetTest(CharSequence charSequence){
System.out.println(charSequence+"實現了多態");
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
StringTest strTest = new StringTest();
strTest.charSetTest(new String("我是String"));
strTest.charSetTest(new StringBuffer("我是StringBuffer"));
strTest.charSetTest(new StringBuilder("我是StringBuilder"));
}
執行結果:
我是String實現了多態
我是StringBuffer實現了多態
我是StringBuilder實現了多態
繼承這個接口的原因就很明顯:
因為String對像是不可變的,StringBuffer和StringBuilder這兩個是可變的,所以我們在構造字符串的過程中往往要用到StringBuffer和StringBuilder。如果那些方法定義String作為參數類型,那麼就沒法對它們用那些方法,先得轉化成String才能用。但StringBuffer和StringBuilder轉換為String再轉換過來很化時間的,用它們而不是直接用String的“加法”來構造新String本來就是為了省時間。
Serializable接口:
繼承該接口,就是表明這個類是是可以別序列化的,這裏就標記了string這個類是可以被序列化的,序列化的定義以及使用時機可以移步這裏。
2.常用方法的使用和源碼解析:
2.1構造方法
string總共提供了15種構造方法:
當然,這麼多的構造方法,只需搞明白四個構造方法就可以了,因為其他的構造方法就是這四個構造方法的調用:
在看構造方法之前,先看看String的屬性
private final char value[];
private int hash; // Default to 0
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
通過屬性了解到,底層聲明了一個final類型的char數組,這個char數組就是String的構造方法的核心,因為String的本質就是字符char(只針對javaSE8 以前的版本),下面來分析有代表的四個構造方法:
-
String()和String(String original)
這兩個構造方法我們平時用的比較經常,源碼如下:
public String() { this.value = "".value; }
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
分析:
通過源碼可以看到,核心還是和String類聲明的char[]數組value屬性建立聯繫,進而來處理這個屬性的值。
在String()這個空構造的方法中,就是屬性char數組的值聲明為一個空的字符串賦值給屬性value.
而在String(String original),也是將方法參數的value的屬性賦值給聲明char[]數組的value屬性,方便String的其他方法對char[]數組處理。
記住,在java的String操作中,大多數情況下還是對char[]數組的操作,這點很重要。
一般情況下定義一個新的字符串,有下面的兩種方式:
String chen = new String("chen"); // 這個我們一般不會使用
String chen = "chen";
我們一般會選擇第二種,這又是什麼原因呢:
其實這兩種聲明的方式在JVM看來時等價的。
劃重點:
但是String password=”chen”,利用了字符串緩衝池,也就是說如果緩衝池中已經存在了相同的字符串,就不會產生新的對象,而直接返回緩衝池中的字符串對象的引用。
如:
String a = "chen";
String b = "chen";
String c = new String("chen");
String d = new String("chen");
System.out.println(a==b);//將輸出"true";因為兩個變量指向同一個對象。利用了字符串的緩衝池
System.out.println(c==d);//將輸出"flase";因為兩個變量不指向同一個對象。雖然值相同,只有用c.equals(d)才能返回true.
所以實際中,建議用第一種,可以減少系統資源消耗。
-
String(char[]vlaue,int offest,int count) 與字符相關的構造方法
源碼如下:
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
分析:
這個構造方法的作用就是傳入指定大小的char[] 數組,指定一個起始位置,然後構造出從起始位置開始計算的指定長度個數的字符串,具體用法:
public static void main(String[] args) {
char[] chars = new char[]{'a','b','c','d'};
// 從chars數組的第二位開始,總數為3個字符的字符串
String rangeChar=new String(chars,1,3);
System.out.println(rangeChar);
}
輸出:
abc
這個構造方法的核心在於:
this.value = Arrays.copyOfRange(value, offset, offset+count);
//而這個方法在追一層,就到了Arrays這個工具類copyOfRange這個方法
public static char[] copyOfRange(char[] original, int from, int to) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
char[] copy = new char[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
其實看到這裏,他的實現原理就基本清楚了,分析copyOfRange()這個方法的執行步驟:
首先是獲取原字符數組的orginal[]要構造字符串的長度,也就是這一行代碼:
int newLength = to - from;
然後異常判斷,並聲明新的數組,來存儲原數組指定長度的值
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
char[] copy = new char[newLength];
將原字符數組指定長度的值拷貝到新數組,返回這個數組:
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
最後再將數組的值賦值給String的屬性value,完成初始化:
this.value = Arrays.copyOfRange(value, offset, offset+count);
歸根結底還是和String聲明的屬性value建立聯繫,完成相關的操作。
-
String(byte[] bytes,int offest,int length,Charset charset) 字節相關的構造方法
這個構造方法的作用就是將指定長度的字節數組,構造成字符串,且還可以指定編碼值:
涉及的源碼如下:
public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charset, bytes, offset, length);
}
// StringCoding中的方法:
static char[] decode(Charset cs, byte[] ba, int off, int len) {
// 1, 構造解碼器
CharsetDecoder cd = cs.newDecoder();
int en = scale(len, cd.maxCharsPerByte());
char[] ca = new char[en];
if (len == 0)
return ca;
boolean isTrusted = false;
if (System.getSecurityManager() != null) {
if (!(isTrusted = (cs.getClass().getClassLoader0() == null))) {
ba = Arrays.copyOfRange(ba, off, off + len);
off = 0;
}
}
cd.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE)
.reset();
if (cd instanceof ArrayDecoder) {
int clen = ((ArrayDecoder)cd).decode(ba, off, len, ca);
return safeTrim(ca, clen, cs, isTrusted);
} else {
ByteBuffer bb = ByteBuffer.wrap(ba, off, len);
CharBuffer cb = CharBuffer.wrap(ca);
try {
CoderResult cr = cd.decode(bb, cb, true);
if (!cr.isUnderflow())
cr.throwException();
cr = cd.flush(cb);
if (!cr.isUnderflow())
cr.throwException();
} catch (CharacterCodingException x) {
// Substitution is always enabled,
// so this shouldn't happen
throw new Error(x);
}
return safeTrim(ca, cb.position(), cs, isTrusted);
}
}
private static char[] safeTrim(char[] ca, int len,
Charset cs, boolean isTrusted) {
if (len == ca.length && (isTrusted || System.getSecurityManager() == null))
return ca;
else
return Arrays.copyOf(ca, len);
}
這個方法構造的方法的複雜之處就是在於對於指定編碼的處理,但是我們如果看完這個方法調用的整個流程最終還是落到
return Arrays.copyOf(ca, len);
返回一個指定編碼的字符數組,然後和String類的value屬性建立聯繫
字節數組構造方法的基本邏輯:就是將字節數組轉化為字符數組,再和String的value屬性建立聯繫,完成初始化。
-
String(StringBuilder builder) 和String(StringBuffer buffer)與String擴展類相關的構造方法:
這個構造方法就是將StringBuilder或者StringBuffer類初始化String類,源碼如下:
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
分析:
核心的代碼還是這一句:
this.value = Arrays.copyOf(builder.getValue(), builder.length());
在往下看builder.getValue(),的源碼
final char[] getValue() {
return value;
}
返回一個字符數組
這樣就能很好理解這個構造方法了: 先利用builder.getValue()將指定的類型轉化為字符數組,通過Arrays.copyOf()方法進行拷貝,將返回的數組賦值給String的屬性value,完成初始化。
2.2常用的方法分析
String的方法大概有60多個,這裏只分析幾個常用的方法,了解其他的方法,可以移步javaSE官方文檔:
-
字符串轉化為字符的方法:charAt(int index)
public char charAt(int index) {
//1. 判斷異常
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
// 2.返回指定位置的字符
return value[index];
}
用法示例:
String StrChar = "chen";
char getChar = StrChar.charAt(1);
System.out.println(getChar);
輸出:
h
2.字符串轉化為字節數組的方法:getBytes()
// 源碼
public byte[] getBytes() {
return StringCoding.encode(value, 0, value.length);
}
// encode的源碼
static byte[] encode(char[] ca, int off, int len) {
String csn = Charset.defaultCharset().name();
try {
// use charset name encode() variant which provides caching.
return encode(csn, ca, off, len);
} catch (UnsupportedEncodingException x) {
warnUnsupportedCharset(csn);
}
try {
return encode("ISO-8859-1", ca, off, len);
} catch (UnsupportedEncodingException x) {
// If this code is hit during VM initialization, MessageUtils is
// the only way we will be able to get any kind of error message.
MessageUtils.err("ISO-8859-1 charset not available: "
+ x.toString());
// If we can not find ISO-8859-1 (a required encoding) then things
// are seriously wrong with the installation.
System.exit(1);
return null;
}
}
用法示例:
String strByte = "chen";
// 將string轉化為字節數組
byte[] getBytes = strByte.getBytes();
// 遍歷輸出
for (byte getByte : getBytes) {
System.out.println(getByte);
}
輸出結果:
99
104
101
110
3.返回字符串中指定字符的下標的方法: indexOf(String str):
這裏的參數為字符串:
這個方法一共涉及了四個方法,源碼如下:
public int indexOf(String str) {
return indexOf(str, 0);
}
public int indexOf(String str, int fromIndex) {
return indexOf(value, 0, value.length,
str.value, 0, str.value.length, fromIndex);
}
static int indexOf(char[] source, int sourceOffset, int sourceCount,
String target, int fromIndex) {
return indexOf(source, sourceOffset, sourceCount,
target.value, 0, target.value.length,
fromIndex);
}
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
// 1. 判斷範圍
if (fromIndex >= sourceCount) {
return (targetCount == 0 ? sourceCount : -1);
}
if (fromIndex < 0) {
fromIndex = 0;
}
if (targetCount == 0) {
return fromIndex;
}
// 2,判斷目標字符串是否時原子符串的子序列,並返回目標序列的第一個字符在原字符序列的索引。
char first = target[targetOffset];
int max = sourceOffset + (sourceCount - targetCount);
for (int i = sourceOffset + fromIndex; i <= max; i++) {
/* Look for first character. */
if (source[i] != first) {
while (++i <= max && source[i] != first);
}
/* Found first character, now look at the rest of v2 */
if (i <= max) {
int j = i + 1;
int end = j + targetCount - 1;
for (int k = targetOffset + 1; j < end && source[j]
== target[k]; j++, k++);
if (j == end) {
/* Found whole string. */
return i - sourceOffset;
}
}
}
return -1;
}
具體執行過程已在方法的註釋中進行了說明:
用法示例:
String strIndex = "chen";
int getIndex = strIndex.indexOf("he");
System.out.println(getIndex);
輸出:
1
注意:也就是輸出字符串中的第一個字符在原子符串中的索引,前提是傳入的參數必須是原子符串的子序列,以上面的列子為例,傳入的字符串序列必須是chen這個字符串的子序列,才能輸出正確的索引,比如傳入的序列不是chen的子序列,輸出為-1
String strIndex = "chen";
int getIndex = strIndex.indexOf("hew");
System.out.println(getIndex);
輸出:
-1
使用這個方法時,這點非常注意。
4.將字符串中的某個字符進行替換:replace(char oldChar, char newChar)
參數就是被替換的字符和新的字符,源碼如下:
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
// 1.遍歷字符數組,找到原子符的位置
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
//2. 聲明一個臨時的字符數組,用來存儲替換后的字符串,
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
// 3. 將原字符數組拷貝到新的字符數組中去
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
// 4.
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
// 3. 初始化一個新的字符串
return new String(buf, true);
}
}
return this;
}
具體的執行邏輯就是註釋的語句。
用法示例:
String strIndex = "chee";
String afterReplace = strIndex.replace('e','n');
System.out.println(afterReplace);
輸出:
chnn
注意:這裏的替換是字符串中的所有與舊字符的相同的字符,比如上面的這個例子,就是將原子符中的e全部替換為n。
5.字符串的分隔:split(String regex) :
源碼如下:
public String[] split(String regex) {
return split(regex, 0);
}
public String[] split(String regex, int limit) {
/* fastpath if the regex is a
(1)one-char String and this character is not one of the
RegEx's meta characters ".$|()[{^?*+\\", or
(2)two-char String and the first char is the backslash and
the second is not the ascii digit or ascii letter.
*/
char ch = 0;
if (((regex.value.length == 1 &&
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
(regex.length() == 2 &&
regex.charAt(0) == '\\' &&
(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
((ch-'a')|('z'-ch)) < 0 &&
((ch-'A')|('Z'-ch)) < 0)) &&
(ch < Character.MIN_HIGH_SURROGATE ||
ch > Character.MAX_LOW_SURROGATE))
{
int off = 0;
int next = 0;
boolean limited = limit > 0;
ArrayList<String> list = new ArrayList<>();
while ((next = indexOf(ch, off)) != -1) {
if (!limited || list.size() < limit - 1) {
list.add(substring(off, next));
off = next + 1;
} else { // last one
//assert (list.size() == limit - 1);
list.add(substring(off, value.length));
off = value.length;
break;
}
}
// If no match was found, return this
if (off == 0)
return new String[]{this};
// Add remaining segment
if (!limited || list.size() < limit)
list.add(substring(off, value.length));
// Construct result
int resultSize = list.size();
if (limit == 0) {
while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
resultSize--;
}
}
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result);
}
return Pattern.compile(regex).split(this, limit);
}
用法示例:
// 將一句話使用空格進行分隔
String sentence = "People who hear thumb up can get rich";
// 使用空格進行分隔
String[] subSequence = sentence.split("\\s");
for (String s : subSequence) {
System.out.println(s);
}
輸出:
People
who
hear
thumb
up
can
get
rich
注意:使用這個方法,對正則表達式有所了解,才能實現更強大的功能,正則表達式的學習,可以移步菜鳥教程
6.實現字符串的指定範圍的切割substring(int beginIndex, int endIndex):
源碼如下:
public String substring(int beginIndex, int endIndex) {
// 1.判斷異常
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
// 2,確定切割的長度
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
// 3.使用構造方法,返回切割后字符串
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
具體的執行邏輯如註釋所示,這個方法的邏輯總體比較簡單:
具體用法:
String sentence = "hhchennn";
String subSequence = sentence.substring(2,6);
System.out.println(subSequence);
輸出:
chen
注意:從源碼了解到,這個方法的在切割的時候,一般將第一個參數包含,包含第二個參數,也就是說上面的例子中在切割後的字符串中包含2這個字符,但是不包含6這個字符。
7.當然處理這些還有一些常用的方法,比如:
// 1.去除字符串前後空格的方法
trim()
//2.大小寫轉換的方法
toLowerCase()
toUpperCase()
//3. 將字符串轉化為數組
toCharArray()
// 4.將基本類型轉化字符串
valueOf(boolean b)
// 5.返回對象本身的字符串形式
toString()
這些方法使用起來都比較簡單,強烈建議看看java官方文檔。
3.面試常問
3.1. 為什麼是不可變的
1、什麼是不可變?
從java角度來講就是說成final的。參考Effective Java
中第15條使可變性最小化
中對不可變類
的解釋:
不可變類只是其實例不能被修改的類。每個實例中包含的所有信息都必須在創建該實例的時候就提供,並且在對象的整個生命周期內固定不變。為了使類不可變,要遵循下面五條規則:
1. 不要提供任何會修改對象狀態的方法。
2. 保證類不會被擴展。 一般的做法是讓這個類稱為 `final` 的,防止子類化,破壞該類的不可變行為。
3. 使所有的域都是 final 的。
4. 使所有的域都成為私有的。 防止客戶端獲得訪問被域引用的可變對象的權限,並防止客戶端直接修改這些對象。
5. 確保對於任何可變性組件的互斥訪問。 如果類具有指向可變對象的域,則必須確保該類的客戶端無法獲得指向這些對象的引用。
當然在Java平台類庫中,包含許多不可變類,例如String ,基本類型的包裝類,BigInteger, BigDecimal等等。綜上所述,不可變類具有一些顯著的通用特徵:類本身是final修飾的;所有的域幾乎都是私有final
的;不會對外暴露可以修改對象屬性的方法。通過查閱String
的源碼,可以清晰的看到這些特徵。
2.為什麼不可變
String real = "chen"
real = "Wei";
下圖就很好解釋了代碼的執行過程:
執行第一行代碼時,在堆上新建一個對象實例chen
,real
是一個指向該實例的引用,引用包含的僅僅只是實例在堆上的內存地址而已。執行第二行代碼時,僅僅只是改變了real
這個引用的地址,指向了另一個實例wei
。所以,正如前面所說過的,不可變類只是其實例不能被修改的類。給real
重新賦值僅僅只是改變了它的引用而已,並不會真正去改變它本來的內存地址上的值。這樣的好處也是顯而易見的,最簡單的當存在多個String
的引用指向同一個內存地址時,改變其中一個引用的值並不會對其他引用的值造成影響。
那麼,String
是如何保持不可變性的呢?結合Effective Java
中總結的五條原則,閱讀它的源碼之後就一清二楚了。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
String
類是final
修飾的,滿足第二條原則:保證類不會被擴展。
分析一下它的幾個域:
private final char value[] :
可以看到Java還是使用字節數組來實現字符串的,並且用final
修飾,保證其不可變性。這就是為什麼String實例不可變的原因。
private int hash :
String的哈希值緩存
private static final long serialVersionUID = -6849794470754667710L :
String對象的 serialVersionUID
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0] :
序列化時使用
其中最主要的域就是value
,代表了String對象的值。由於使用了private final
修飾,正常情況下外界沒有辦法去修改它的值的。正如第三條使所有的域都是final的。和第四條使所有的域都成為私有的所描述的。難道這樣一個private
加上final
就可以保證萬無一失了嗎?看下面代碼示例:
final char[] value = {'a', 'b', 'c'};
value[2] = 'd';
這時候的value
對像在內存中已經是a b d
了。其實final
修飾的僅僅只是value
這個引用,你無法再將value
指向其他內存地址,例如下面這段代碼就是無法通過編譯的:
final char[] value = {'a', 'b', 'c'};
value = {'a', 'b', 'c', 'd'};
所以僅僅通過一個final
是無法保證其值不變的,如果類本身提供方法修改實例值,那就沒有辦法保證不變性了。Effective Java中的第一條原則不要提供任何會修改對象狀態的方法。String類也很好的做到了這一點。在String
中有許多對字符串進行操作的函數,例如substring
concat
replace
replaceAll
等等,這些函數是否會修改類中的value
域呢?我們看一下concat()
函數的內部實現:
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
注意其中的每一步實現都不會對value
產生任何影響。首先使用Arrays.copyOf()
方法來獲得value
的拷貝,最後重新new
一個String對像作為返回值。其他的方法和contact
一樣,都採取類似的方法來保證不會對value
造成變化。的的確確,String
類中並沒有提供任何可以改變其值的方法。相比final
而言,這更能保障String不可變。
3.不可變類的好處:
Effective Java
中總結了不可變類的特點。
- 不可變類比較簡單。
- 不可變對象本質上是線程安全的,它們不要求同步。不可變對象可以被自由地共享。
- 不僅可以共享不可變對象,甚至可以共享它們的內部信息。
- 不可變對象為其他對象提供了大量的構建。
- 不可變類真正唯一的缺點是,對於每個不同的值都需要一個單獨的對象。
3.2. 使用什麼方式可以改變String類的不可變性
當然使用反射,java的反射機制可以做到我們平常做不到的很多事情:
String str = "chen";
System.out.println(str);
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[]) field.get(str);
value[1] = 'a';
System.out.println(str);
執行結果:
chen
caen
3.3. String和stringBuffer和StringBuilder的區別
從以下三個方面來考慮他們之間的異同點:
1.可變和不可變性:
String: 字符串常量,在修改時,不會改變自身的值,若修改,就會重新生成新的字符串對象。
StringBuffer: 在修改時會改變對象本身,不會生成新的對象,使用場景:對字符經常改變的情況下,主要方法: append(),insert() 等。
2.線程是否安全
String: 定義之後不可改變,線程安全
String Buffer: 是線程安全的,但是執行效率比較低,適用於多線程下操作字符串緩衝區的大量數據。
StringBuilder: 線程不安全的,適用於單線程下操作字符串緩衝區的大量數據
3.共同點
StringBuilder和StringBuffer有共有的父類AbstractStringBuilder(抽象類)。
StringBuilder,StringBuffer的方法都會調用AbstractStringBuilder中的公共方法,如: super().append()…
只是StringBuffer會在方法上加上synchronized關鍵字,進行同步。
4.優秀的工具包推薦
4.1guava
Guava工程包含了若干被Google的Java項目廣泛依賴的核心庫,例如:集合[collections] 、緩存[caching] 、原生類型支持[primitives support] 、併發庫[concurrency libraries] 、通用註解[common annotations] 、字符串處理[string processing] 、I/O 等等。所有這些工具每天都在被Google的工程師應用在產品服務中。具體的中文參考文檔,Guava中文參考文檔。
4.2 Hutool
Hutool是一個Java工具包,也只是一個工具包,它幫助我們簡化每一行代碼,減少每一個方法,讓Java語言也可以“甜甜的”。Hutool最初是我項目中“util”包的一個整理,後來慢慢積累並加入更多非業務相關功能,並廣泛學習其它開源項目精髓,經過自己整理修改,最終形成豐富的開源工具集。Hutool參考文檔
追本溯源,方能闊步前行
參考資料:
參考博客:
https://juejin.im/post/59cef72b518825276f49fe40
https://www.cnblogs.com/ChrisMurphy/p/4760197.html
參考書籍: java官方文檔《深入理解JVM虛擬機》
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※回頭車貨運收費標準
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※推薦評價好的iphone維修中心
※教你寫出一流的銷售文案?
※台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!
※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!