close

Source: http://www.cnblogs.com/5207/p/5892583.html

字符串為什么這么重要

寫了多年java的開發應該對String不陌生,但是我卻越發覺得它陌生。每學一門編程語言就會與字符串這個關鍵詞打不少交道。看來它真的很重要。

字符串就是一系列的字符組合的串,如果寫過C/C++的應該就了解,在字符串的操作上會有許多操作的函數與類,用于簡化代碼的開發。一方面是因為字符串在代碼中會頻繁用到,另一方面是因為字符串的操作非常麻煩。

最初我知道String的特殊待遇就是在delphi中,因為String在delphi里是一個關鍵字存在,與其他的基本類型是不一樣的。那時就了解到了許多相關的知識。在java/.net也都對string做了專門的處理,可見重要性。

正因為字符串在程序中用的多,而且操作也多這就會帶來內存占用與性能的問題,所需要特殊的關照一下,想象一下一個日志記錄系統一天時間得用上多少字符串變量。

了解一下java中的String

java中提供了String類支持字符串的功能,畢竟字符串本質就是一堆字符的組合,那么就來看看它有什么特點吧。

  • String的特點

String把字符串還是存放在一個char數組中的,數據的操作圍繞它展開,但有點特別的地方,代碼如下

private final char value[];

可以發現這個char value[]是加了final的,也就是說一旦創建了值就不可變。這樣就會導致每一次創建String只會有一個值,再對其進行字符串操作也必須生成新的值。java對這個處理使用了字符串常量池的概念。就是把字符串丟到一個池里,如果相同就用相同的。當然這也有個前提,就是要用下面的方式

String s = "abc";

這樣做的時候jvm會在編譯期就確定了,在運行時會先在常量池里查找是否有"abc",沒有就添加并返回,有的話返回常量池的對象。這樣做的好處是對于相同的字符串就不需要重復創建啦。 但是如果使用下面的代碼

String s1 = new String("abc");

這個時候情景就變了,這里jvm會在堆棧里創建一個對象s1,只不過s1里的value也是指向"abc"的。后面在看字符串比較的時候會發現區別。

  • 字符串比較 看一段代碼:
String s = "abc";
String s1 = "abc";

if (s == s1) {
    System.out.println("s == s1");
}

問:這時s==s1嗎?

答案是相等的,為什么呢?其實jvm會在s1創建時去常量區查找是否有相同值的字符串,如果有就返回給s1,這樣s1就和s指向了同一個字符串,所以是相等的。

但是還有一種情況就不一樣,

String s = "abc";
String s3 = new String("abc");
if (s == s3) {
	System.out.println("s == s3");
}
else {
    System.out.println("s != s3");
}

這個時候應該print出s != s3,這是因為new一個String對象后確實會創建一個新的變量。所以使用==比較的話自然就返回false了。

用到equals比較呢?

String s = "abc";
String s2 = new String("abc");
if (s.equals(s2)) {
	System.out.println("s = s2");
}
else {
    System.out.println("s != s2");
}

打印是s = s2,因為==是用于比較兩個地址,而equals是用于比較兩個變量的值。可以看一下equals的代碼

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    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;
}

在equals中,先是比較是否地址相同,如果不相同比較value,因為value都是"abc"自然就返回true。

  • intern方法

String里有一個intern方法,我們可以先試一下面的代碼。

String s = "abc";
String s3 = new String("abc");
if (s.intern() == s3.intern()) {
	System.out.println("s.intern = s3.intern");
}
else {
    System.out.println("s.intern != s3.intern");
}

還是上面的s和s3,如果使用各自的intern方法返回的值比較則會輸出s.intern = s3.intern。找了找資料結合注釋了解到,這個intern方法其實是從字符串常量池里返回當前字符串,如果當前字符串已經存在了則返回當前字符串,如果當前字符串不存在,則將當前字符串放入常量池再返回。

有了這個解釋就明白了,s和s3都通過intern返回的那么都是常量池里的"abc"咯,所以intern比較時是相等的。

認識一下StringBuffer和StringBuilder

  • StringBuffer和StringBuilder哪一個是線程安全的?

面試時遇到的這個問題,我突然有點懵,沒太注意過這兩個類,而且印象中java里只有一個StringBuffer呀?回來看了一下代碼原來StringBuffer是線程安全的,也就是在字符串操作的方法上都有synchronized。

于是打開代碼注釋發現是Jdk1.5才開始有的StringBuilder,而且在后面版本加了個不加鎖的類,看樣子是解決非并發場景下的效率問題,不加鎖對于操作大字符串還是有性能提升的。

嗯,不錯,get了一個小知識。

出于好奇看了一下這兩個類的代碼,與String真有些類似,只不過這時的chat[] 已經是不帶final的咯,這樣就避免了String類操作時產生一堆字符串對象的問題。

char[] value;
  • StringBuffer和StringBuilder的作用

既然已經有了String,那這兩個家伙有什么用呢?其實問題還是要和String的原理有關系。因為String是通過常量池管理的,這樣解決的是相同字符串重復創建的問題,但大部分字符串都是不一樣的,特別是在做字符串拼接操作時,如果用String的+進行拼接就會產生大量的字符串常量,非常的消耗性能與空間。

為解決這個問題就用到StringBuffer,本質上也就是通過一個可變的字符序列,在字符串操作時不需要生成新的對象,從而提升內存使用。

看看StringBuffer是怎么提升這個拼接性能的吧。 查看StringBuffer/StringBuilder的代碼(JDK1.5+)發現它們都繼承于AbstractStringBuilder。很多的代碼其實都是在AbstractStringBuilder里完成的。因為這個問題由拼接引出的,在此我們就主要關注一下append方法吧。

public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    ensureCapacityInternal(count + len);//確定容量
    str.getChars(0, len, value, count);//取出str的字符放入到value數組中
    count += len;//count累加
    return this;
}

代碼還是比較清楚的,整個過程最重要的就是使用String的getChars方法將str的值寫入到當前對象的value中。而String的getChars方法如下:

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
    if (srcBegin < 0) {
        throw new StringIndexOutOfBoundsException(srcBegin);
    }
    if (srcEnd > value.length) {
        throw new StringIndexOutOfBoundsException(srcEnd);
    }
    if (srcBegin > srcEnd) {
        throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
    }
    System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

可以看出最終是做了一個數組的復制,因為在AbstractStringBuilder中的value是個可變的char數組,這樣的話對于字符串操作只需要在char數組上進行即可。不會像String那樣生成新對象,所以說自然就變的高效了。

 

注:此文章為原創,歡迎轉載,請在文章頁面明顯位置給出此文鏈接!
若您覺得這篇文章還不錯請點擊下右下角的推薦,非常感謝!
http://www.cnblogs.com/5207/

不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 AutoPoster 的頭像
    AutoPoster

    互聯網 - 大數據

    AutoPoster 發表在 痞客邦 留言(0) 人氣()