前言
本文主要介紹了關于jdk源碼分析之string、stringbuilder和stringbuffer的相關內容,分享出來供大家參考學習,下面話不多說了,來一起看看詳細的介紹吧
string類的申明
1
2
|
public final class string implements java.io.serializable, comparable<string>, charsequence {…} |
string類用了final修飾符,表示它不可以被繼承,同時還實現了三個接口, 實現serializable接口表示string類可被序列化;實現comparable<t> 接口主要是提供一個compareto 方法用于比較string字符串;還實現了charsequence 接口,這個接口代表的是char值得一個可讀序列(charbuffer, segment, string, stringbuffer, stringbuilder也都實現了charsequence接口)
string主要字段、屬性說明
1
2
3
4
5
6
7
8
|
/*字符數組value,存儲string中實際字符 */ private final char value[]; /*字符串的哈希值 默認值0*/ private int hash; /*字符串的哈希值 默認值0*/ /*一個比較器,用來排序string對象, comparetoignorecase方法中有使用 */ public static final comparator<string> case_insensitive_order = new caseinsensitivecomparator(); |
string 部分方法分析
string類提供了系列的構造函數,其中有幾個都已經不推薦使用了,如下圖:
構造函數
以下是兩個常用的構造函數的實現:
1
2
3
4
5
6
7
8
9
10
11
|
//string str = new string(“123”) public string(string original) { this .value = original.value; this .hash = original.hash; } //string str3 = new string(new char[] {'1','2','3'}); public string( char value[]) { //將字符數組值copy至value this .value = arrays.copyof(value, value.length); } |
boolean equals(object anobject)
string 類重寫了 equals 方法,將此字符串與指定的對象比較。當且僅當該參數不為 null,并且是與此對象表示相同字符序列的 string 對象時,結果才為 true。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public boolean equals(object anobject) { //直接將對象引用相比較,相同返回true if ( this == anobject) { return true ; } //比較當前對象與anobject的字符序列value if (anobject instanceof string) { string anotherstring = (string)anobject; int n = value.length; if (n == anotherstring.value.length) { char v1[] = value; char v2[] = anotherstring.value; int i = 0 ; while (n-- != 0 ) { if (v1[i] != v2[i]) return false ; i++; } return true ; } } return false ; } |
int compareto(string anotherstring)
逐位比較兩個字符串的字符序列,如果某一位字符不相同,則返回該位的兩個字符的unicode 值的差,所有位都相同,則計算兩個字符串長度之差,兩個字符串相同則返回0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public int compareto(string anotherstring) { int len1 = value.length; int len2 = anotherstring.value.length; //取長度較小的字符串的長度 int lim = math.min(len1, len2); char v1[] = value; char v2[] = anotherstring.value; int k = 0 ; while (k < lim) { //將兩個字符串的字符序列value逐個比較,如果不等,則返回該位置兩個字符的unicode 之差 char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; //返回unicode 之差 } k++; } //長度較小的字符串所有位都比較完,則返回兩個字符串長度之差 //如果兩個字符串相同,那么長度之差為0,即相同字符串返回0 return len1 - len2; } |
comparetoignorecase(string str)方法實現于此類似,比較時忽略字符的大小寫,實現方式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public int compare(string s1, string s2) { int n1 = s1.length(); int n2 = s2.length(); int min = math.min(n1, n2); for ( int i = 0 ; i < min; i++) { char c1 = s1.charat(i); char c2 = s2.charat(i); if (c1 != c2) { c1 = character.touppercase(c1); c2 = character.touppercase(c2); if (c1 != c2) { c1 = character.tolowercase(c1); c2 = character.tolowercase(c2); if (c1 != c2) { // no overflow because of numeric promotion return c1 - c2; } } } } return n1 - n2; } |
native string intern()
當調用 intern 方法時,如果池已經包含一個等于此 string 對象的字符串(用 equals(object) 方法確定),則返回池中的字符串。否則,將此 string 對象添加到池中,并返回此 string 對象的引用。
所有字面值字符串和字符串賦值常量表達式都使用 intern 方法進行操作,例如:string str1 = "123";
string內存位置:常量池or堆
string對象可以直接通過字面量創建,也可以通過構造函數創建,有什么區別呢?
1.通過字面量或者字面量字符串通過”+”拼接的方式創建的string對象存儲在常量池中,實際創建時如果常量池中存在,則直接返回引用,如果不存在則創建該字符串對象
2.使用構造函數創建字符串對象,則直接在堆中創建一個string對象
3.調用intern方法,返回則會將該對象放入常量池(不存在則放入常量池,存在則返回引用)
下面舉例說明string對象內存分配情況:
1
2
3
4
5
6
7
|
string str1 = new string( "123" ); string str2 = "123" ; string str3 = "123" ; string str4 = str1.intern(); system.out.println(str1==str2); // false str1在堆中創建對象,str2在常量池中創建對象 system.out.println(str2==str3); // true str2在常量池中創建對象,str3直接返回的str2創建的對象的引用 所以str2和str3指向常量池中同一個對象 system.out.println(str4==str3); // true str4返回常量池中值為"123"的對象,因此str4和str2、str3都相等 |
關于字符串拼接示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class stringtest { public static final string x = "abc" ; // 常量x @test public void test() { string str5 = new string( "abc" ); string str6 = str5+ "def" ; //堆中創建 string str7 = "abc" + "def" ; //常量池 string str8 = x+ "def" ; //x為常量,值是固定的,因此x+"def"值已經定下來為abcdef,實際上編譯后得代碼相當于string str8 = "abcdef" string str9 = "abc" ; string str10 = str9+ "def" ; //堆中 system.out.println(str6==str7); //false system.out.println(str8==str7); //true system.out.println(str10==str7); //false system.out.println(x==str9); //true } } |
反編譯后的代碼看一下便一目了然:
內存分配如下:
string、stringbuffer、stringbuilder
由于string類型內部維護的用于存儲字符串的屬性value[]字符數組是用final來修飾的:
1
2
|
/** the value is used for character storage. */ private final char value[]; |
表明在賦值后可以再修改,因此我們認為string對象一經創建后不可變,在開發過程中如果碰到頻繁的拼接字符串操作,如果使用string提供的contact或者直接使用”+”拼接字符串會頻繁的生成新的字符串,這樣使用顯得低效。java提供了另外兩個類:stringbuffer和stringbuilder,用于解決這個問題:
看一下下面的代碼:
1
2
3
4
5
6
7
8
9
|
string str1= "123" ; string str2= "456" ; string str3= "789" ; string str4 = "123" + "456" + "789" ; //常量相加,編譯器自動識別 string str4=“123456789” string str5 = str1 + str2 + str3; //字符串變量拼接,推薦使用stringbuilder stringbuilder sb = new stringbuilder(); sb.append(str1); sb.append(str2); sb.append(str3); |
下面是stringbuilder類的實現,只截取了分析的部分代碼:
1
2
3
4
5
6
7
8
9
|
public final class stringbuilder extends abstractstringbuilder implements java.io.serializable, charsequence { //拼接字符串 @override public stringbuilder append(string str) { //調用父類abstractstringbuilder.append super.append(str); return this; } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
abstract class abstractstringbuilder implements appendable, charsequence { /** * 存儲字符串的字符數組,非final類型,區別于string類 */ char [] value; /** * the count is the number of characters used. */ int count; public abstractstringbuilder append(string str) { if (str == null ) return appendnull(); int len = str.length(); //檢查是否需要擴容 ensurecapacityinternal(count + len); //字符串str拷貝至value str.getchars( 0 , len, value, count); count += len; return this ; } private void ensurecapacityinternal( int minimumcapacity) { // overflow-conscious code // minimumcapacity=count+str.length //拼接上str后的容量 如果 大于value容量,則擴容 if (minimumcapacity - value.length > 0 ) { //擴容,并將當前value值拷貝至擴容后的字符數組,返回新數組引用 value = arrays.copyof(value, newcapacity(minimumcapacity)); } } //stringbuilder擴容 private int newcapacity( int mincapacity) { // overflow-conscious code // 計算擴容容量 // 默認擴容后的數組長度是按原數(value[])組長度的2倍再加上2的規則來擴展,為什么加2? int newcapacity = (value.length << 1 ) + 2 ; if (newcapacity - mincapacity < 0 ) { newcapacity = mincapacity; } return (newcapacity <= 0 || max_array_size - newcapacity < 0 ) ? hugecapacity(mincapacity) : newcapacity; } } |
stringbuffer和stringbuilder用一樣,內部維護的value[]字符數組都是可變的,區別只是stringbuffer是線程安全的,它對所有方法都做了同步,stringbuilder是線程非安全的,因此在多線程操作共享字符串變量的情況下字符串拼接處理首選用stringbuffer, 否則可以使用stringbuilder,畢竟線程同步也會帶來一定的消耗。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://www.cnblogs.com/ashleyboy/p/9063153.html