java基礎解析系列(一)---String、StringBuffer、StringBuilder
前言:本系列的主題是平時容易疏忽的知識點,只有基礎扎實,在編碼的時候才能更注重規范和性能,在出現bug的時候,才能處理更加從容。
我的博客目錄
String
==問題
String s6=new String("jiajun");
String s1="jiajun";
String s2="jiajun";
System.out.println(s1==s2);//true
System.out.println(s1==s6);//false
- 看常量池中是否已有此字符串,如果有,將指針指向這個字符串
- 如果使用new來創建字符串對象,那么這個字符串是存放在堆中,無論堆中是否已有這個對象
String對象改變
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
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);
}
- 從源碼可以看出,任何操作都是創建一個新的對象,不影響原對象
StringBuffer和StringBuidler
初始容量
- StringBuilder和StringBuffer的構造參數來初始化容量
public StringBuilder() {
super(16);
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
- 默認情況下容量為16
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
- 從源碼看出,在執行append方法的時候,會執行ensureCapacityInternal方法來保證容量,而如果超出容量的話,會重新創建一個char數組,並將舊的字符數組復制到新的字符數組
線程安全
public synchronized StringBuffer append(StringBuffer sb) {
toStringCache = null;
super.append(sb);
return this;
}
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
public StringBuilder append(StringBuffer sb) {
super.append(sb);
return this;
}
public StringBuilder append(String str) {
super.append(str);
return this;
}
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
- 可以看出,String的方法是加了synchronzied,也就加了鎖,那么而在單線程的情況下或者不用考慮線程安全的情況下,那么StringBuilder的性能是更高的
toString方法
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
- 通過源碼發現,toString方法會創建一個新的String對象
性能試驗
間接相加和直接相加
public class d {
public static void main(String[] args) {
String s="I"+"love"+"jiajun";
String s1="I";
String s2="love";
String s3="jiajun";
String s4=s1+s2+s3;
}
}
- 通過反編譯的結果可以看出,第一種方式字符串直接相加,在編譯器就直接優化了”Ilovejiajun“
- 而第二種方式間接相加,從結果可以看出,是先創建一個StringBuilder,然后再apend,最后再toString方法,可以發現性能比第一種低
public class d {
public static void main(String[] args) {
String s="I"+"love"+"jiajun";
String s1="I";
String s2=s1+"lovejiajun";
System.out.println(s==s2);
}
}
- 同樣從反編譯的結果可以看出,第二種方式並沒有被優化,也是通過StringBuilder來實現的,最后通過toString方法創建一個String對象,所以返回的false
- 但是當s1是用final修飾的卻是不一樣的,虛擬機會對其進行優化,所以不會像之前一樣創建一個StringBuilder,最后在堆中產生一個對象
public class d {
public static void main(String[] args) {
final String s1="I";
String s2=s1+"lovejiajun";
String s3="Ilovejiajun";
//s1==s3
}
}
用+和用append
public class Demo3 {
public static void main(String[] args) {
run1();
run2();
}
public static void run1() {
long start = System.currentTimeMillis();
String result = "";
for (int i = 0; i < 10000; i++) {
result += i;
}
System.out.println(System.currentTimeMillis() - start);
}
public static void run2() {
long start = System.currentTimeMillis();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 10000; i++) {
builder.append(i);
}
System.out.println(System.currentTimeMillis() - start);
}
//輸出:223 1
- 從實驗發現,用append效率更高,從實驗一發現,當字符串相加的時候,實際上每次都會重新初始化StringBuilder然后執行相加,這樣效率並不高
初始化容量
public class Demo3 {
public static void main(String[] args) {
test1();
test2();
}
public static void test1() {
StringBuilder sb = new StringBuilder(7000000);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
sb.append("jiajun");
}
long end=System.currentTimeMillis()-start;
System.out.println(end);
}
public static void test2() {
StringBuilder sb = new StringBuilder();
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
sb.append("jiajun");
}
long end=System.currentTimeMillis()-start;
System.out.println(end);
}
//輸出:18 26
}
- 通過實驗可以看出,適當的初始化容量可以提高性能,因為當不初始化容量的時候,如果此時append超出容量,那么將會從新創建一個char數組,並且進行復制
總結
- 用new創建對象的時候,會在堆中創建對象,而如果是直接用引號形式的話,會先看常量池是否有此字符串,有的話指向常量池的字符串
- StringBuilder是非線程安全的,StringBuffer是線程安全的
- 使用StringBuilder和StringBuffer的時候最好初始化一個合適的容量,因為如果默認容量不夠的話,會重新創建一個char數組,再進行復制
- 字符串相加的時候,直接相加的時候,編譯器會進行優化,而如果是間接相加的時候,實際上會創建一個StringBuilder來進行append
我覺得分享是一種精神,分享是我的樂趣所在,不是說我覺得我講得一定是對的,我講得可能很多是不對的,但是我希望我講的東西是我人生的體驗和思考,是給很多人反思,也許給你一秒鍾、半秒鍾,哪怕說一句話有點道理,引發自己內心的感觸,這就是我最大的價值。(這是我喜歡的一句話,也是我寫博客的初衷)
作者:jiajun 出處: http://www.cnblogs.com/-new/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】,希望能夠持續的為大家帶來好的技術文章!想跟我一起進步么?那就【關注】我吧。