關於String類源碼的解析
請先移步這篇文章,對String的原理講的很透徹:
https://www.cnblogs.com/zhangyinhua/p/7689974.html#_lab2_1_2
接下來的主要是一些使用上的總結:
String的valueOf()根據不同的入參,會有不同的結果,這是典型的重載,由於實在是太多了,所以接下來我會選擇性的選擇部分進行介紹,讓大家少踩坑。
1.入參是Object,先上源碼:
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
在賦值的時候會判斷是否為null,若是,則會返回"null"字元串,若否,則會調用父類Object的toString方法(這個就不重點關注了)
接下來我們來看個具體案例:
String test = null;
test = test + "age";
System.out.println(test);
輸出如下:
nullage
2. 入參為boolean,源碼如下:
public static String valueOf(boolean b) {
return b ? "true" : "false";
}
boolean型的數據是直接根據true與false,使用三木運算符判斷,直接返回相應字元串,這個也是我們平時不太會關注的地方
案例如下:
boolean boolTrue = true;
String boolStr = String.valueOf(boolTrue) + " === ";
System.out.println("boolean -- " + boolStr);
輸出如下:
boolean -- true ===
上述兩點是在文章的基礎上在進行補充的,接下來我們再來瞭解下字元常量池。再上面的文章中已經有介紹了,我想通過一些實例來驗證下:
String a = "abc";
String b = "abc";
String c = new String("abc");
// true
System.out.println(a==b);
// true
System.out.println(a.equals(b));
// false
System.out.println(a==c);
// true
System.out.println(a.equals(c));
看了上面的文章我們知道new String()實際上是在堆內存中開闢了兩塊空間,先產生了一個匿名對象"abc",之後new 的時候在產生一個對象"abc",並且c指向後產生的這個匿名對象的內存地址,之前的"abc",沒有任何對象指向這塊地址,變成了垃圾。所以在使用==進行比較的時候,兩個的地址不一致,等式不成立。但是這個時候如果我們想讓 a == c,這個操作成立的話,我們需要手動將new String()這個對象壓入到字元串常量池中,直接使用new String().intern()操作即可。代碼如下:
String str = "abc";
String newStr = new String("abc").intern();
System.out.println("手動入池之後, -- " + (str == newStr));
輸出結果如下:
手動入池之後, -- true
PS:因為JDK8新增了一個String.join方法,這個方法是用來拼接字元串的,源碼以及注釋中demo如下:
// demo message returned is: "Java-is-cool"
String message = String.join("-", "Java", "is", "cool");
public static String join(CharSequence delimiter, CharSequence... elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
// Number of elements not likely worth Arrays.stream overhead.
StringJoiner joiner = new StringJoiner(delimiter);
for (CharSequence cs: elements) {
joiner.add(cs);
}
return joiner.toString();
}
上面的源碼還不大看的明白,具體意思是啥,我們在看看add這個的源碼:
public StringJoiner add(CharSequence newElement) {
prepareBuilder().append(newElement);
return this;
}
這個prepareBuilder()是啥呢,再看看。
private StringBuilder prepareBuilder() {
if (value != null) {
value.append(delimiter);
} else {
value = new StringBuilder().append(prefix);
}
return value;
}
原來是在使用add方法的時候,會先append這個分割符,在append單個字元串,可以看到這個底層是使用StringBuilder方法來實現的。
這個時候我們在追查下StringBuilder的源碼,看看有沒有什麼需要注意的:
@Override
public StringBuilder append(CharSequence s) {
super.append(s);
return this;
}
繼承父類的append方法,在追蹤下:
@Override
public AbstractStringBuilder append(CharSequence s) {
if (s == null)
return appendNull();
if (s instanceof String)
return this.append((String)s);
if (s instanceof AbstractStringBuilder)
return this.append((AbstractStringBuilder)s);
return this.append(s, 0, s.length());
}
上面的appendNull()方法非常關鍵,在AbstractStringBuilder方法中所有append方法重載第一行都是這個判斷。我們再來看看appendNull方法實現代碼:
private AbstractStringBuilder appendNull() {
int c = count;
// 這個方法就不深究了,不用管它
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = n;
value[c++] = u;
value[c++] = l;
value[c++] = l;
count = c;
return this;
}
從源碼可以看到,他會直接添加null幾個字元,這個和String.valueOf(Object obj)方法是一致的判斷。所以通過這一長串的源碼深究,我們知道了這個方法的使用方式以及注意事項,接下來我們再和StringBuilder比較下效率如何:
final long time1 = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
String aa = String.join("","bb","cc","dd","ee");
}
final long time2 = System.currentTimeMillis();
System.out.println("111 --- " + (time2 - time1));
for (int i = 0; i < 10000; i++) {
StringBuilder sb = new StringBuilder();
sb.append("bb")
.append("cc")
.append("dd")
.append("ee");
String aa = sb.toString();
}
System.out.println("222 --- " (System.currentTimeMillis() - time2));
輸入如下:
111 --- 22
222 --- 8
雖然String.join底層也是使用StringBuilder來實現的,但是還是直接使用StringBuilder的效率最高
推薦閱讀: