天天看點

String拼接的問題

轉自: http://blog.csdn.net/izard999/article/details/6708433

網上剖析String的不少,關于其他的String的知識我就不累贅去說了!

本文隻解釋下我在面試中遇到的String拼接的問題以及最近看到了網上的一道機試題跟這個有關系, 是以就想把自己對String拼接的了解分享給大家!

去華為面試的時候, 第一筆試題就讓我費神去想了, 回來在機子上運作結果, 發現自己當時答錯了, 于是就狠下心來花了點時間研究這個:

String s = null;  
s += "abc";  
System.out.println(s);  
           

答案是nullabc!

就這三行代碼, 我問了不下于50個人, 有資深的人也有新手的, 在不運作的情況下全答錯了。! 可見現在學java的人有很多人都是速成的,而且這種原理級而又看似不怎麼實用的東西幾乎沒什麼人去研究, 但是後面說的機試如果能知道String拼接的原理的話。将很容易就解決!

很早的時候我就知道String拼接中間會産生StringBuilder對象(JDK1.5之前産生StringBuffer),但是當時也沒有去深究内部, 導緻在華為筆試此題就錯了!

運作時, 兩個字元串str1, str2的拼接首先會調用 String.valueOf(obj),這個Obj為str1,而String.valueOf(Obj)中的實作是return obj == null ? “null” : obj.toString(), 然後産生StringBuilder, 調用的StringBuilder(str1)構造方法, 把StringBuilder初始化,長度為str1.length()+16,并且調用append(str1)! 接下來調用StringBuilder.append(str2), 把第二個字元串拼接進去, 然後調用StringBuilder.toString傳回結果!

是以那道題答案的由來就是StringBuilder.append(“null”).append(“abc”).toString();

大家看了我以上的分析以後, 再碰到諸如此類的面試題應該不會再出錯了!

那麼了解String拼接有什麼用呢?

在做多線程的時候, 往往會用到一個同步螢幕對象去同步一個代碼塊中的代碼synchronized(Obj), 對同一個對象才會互斥,不是同一個對象就不會互斥!

這裡有個機試題,

現有程式同時啟動了4個線程去調用TestDo.doSome(key, value)方法,由于TestDo.doSome(key, value)方法内的代碼是先暫停1秒,然後再輸出以秒為機關的目前時間值,是以,會列印出4個相同的時間值,如下所示:

4:4:1258199615

1:1:1258199615

3:3:1258199615

1:2:1258199615

請修改代碼,如果有幾個線程調用TestDo.doSome(key, value)方法時,傳遞進去的key相等(equals比較為true),則這幾個線程應互斥排隊輸出結果,即當有兩個線程的key都是”1”時,它們中的一個要比另外其他線程晚1秒輸出結果,如下所示:

4:4:1258199615

1:1:1258199615

3:3:1258199615

1:2:1258199616

總之,當每個線程中指定的key相等時,這些相等key的線程應每隔一秒依次輸出時間值(要用互斥),如果key不同,則并行執行(互相之間不互斥)。原始代碼如下:

view plain

package syn;

//不能改動此Test類

public class Test extends Thread{

private TestDo testDo;  
private String key;  
private String value;  

public Test(String key,String key2,String value){  
    this.testDo = TestDo.getInstance();  
    /*常量"1"和"1"是同一個對象,下面這行代碼就是要用"1"+""的方式産生新的對象, 
    以實作内容沒有改變,仍然相等(都還為"1"),但對象卻不再是同一個的效果*/  
    this.key = key+key2;   
    this.value = value;  
}  


public static void main(String[] args) throws InterruptedException{  
    Test a = new Test("1","","1");  
    Test b = new Test("1","","2");  
    Test c = new Test("3","","3");  
    Test d = new Test("4","","4");  
    System.out.println("begin:"+(System.currentTimeMillis()/1000));  
    a.start();  
    b.start();  
    c.start();  
    d.start();  
}  

public void run(){  
    testDo.doSome(key, value);  
}  
           

}

class TestDo {

private TestDo() {}  
private static TestDo _instance = new TestDo();   
public static TestDo getInstance() {  
    return _instance;  
}  

public void doSome(Object key, String value) {  

    // 以大括号内的是需要局部同步的代碼,不能改動!  
    {  
        try {  
            Thread.sleep(1000);  
            System.out.println(key+":"+value + ":"  
                    + (System.currentTimeMillis() / 1000));  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}  
           

}

此題解題的思路有很多種,不可或缺的步驟就是在doSome方法内部用synchronized(o)把那個寫了注釋的代碼塊同步, 有些人肯定會說:

我直接synchronized(key),不就完了麼.? 這類人肯定是新手級别的了!

上面說了,synchronized(Obj), 對同一個對象才會互斥,不是同一個對象就不會互斥! 大家請看下Test類中的構造方法裡面對key做了什麼處理?

this.key = key + key2;

關于字元串的拼接, 如果是兩個常量的拼接, 那麼你無論拼接多少下都是同一個對象, 這個是編譯時 編譯器自動去優化的(想知道具體原理的自己去網上搜下).

[java] view plaincopy

String a = “a” + “b”;

String b = “a” + “b”;

System.out.println(a == b);

這段代碼輸出true沒有問題

但是一旦涉及到變量了, 我在上面标紅加粗的運作時, 此時拼接字元串就會産生StringBuilder, 然而拼接完傳回的字元串是怎麼傳回的呢?

在StringBuilder.toString()中的實作是new String(char value[], int offset, int count), 既然是建立String傳回的, 那麼調用一次toString,就是一個不同的對象

[java] view plaincopy

String a = “a”;

String b = “b”;

String s1 = a + b;

String s2 = a + b;

System.out.println(s1 == s2);

這個輸出就是false!

是以在那道機試題中, 就不能直接用synchronized(key)去同步了, 如果你完完全全很耐心的看完本文, 那麼應該知道如何用synchronized(key)同步那段代碼了!

不錯, 就是修改Test構造方法中的 this.key = key + key2;為this.key = key;

因為字元串不涉及到拼接的時候, 隻要不new, 多少都是指向同一個對象!

當然這道多線程的題你也可以把那個key丢到集合裡面去,用集合去的contains(obj)去判斷,如果集合中存在, 就取集合中的, 否則往集合中添加,但是記住一定要使用并發包下面的集合, 否則可能會抛出ConcurrentModificationException

繼續閱讀