淺談java中的String、StringBuffer、StringBuilder類的區別以及關係

在java中最常見的使用就是對字符串的操作:首先先說一下對字符串的理解:字符串就是一連串字符序列,Java提供了String和StringBuffer兩個類來封裝字符串,並提供一系列方法來操作字符串對象。接下來對它們一一描述:
String類是不可變類:
即一旦一個String對象被創建以後,包含在這個對象中的字符串是不可改變的,直到這個對象被銷燬。
StringBuffer類:
在java中則代表一個字符序列可變的字符串,即一個StringBuffer被創建以後,StringBuffer提供的append()、insert()、reverse()、setChatAt()、setLength()等方法可以改變這個字符串對象的字符序列。一旦通過StringBuffer生成了最終想要的字符串,就可以調用它的toString()方法將其轉換爲一個String對象。
StringBuilder類:
它是在jdk1.5新增的一個StringBuilder類,它也代表字符串對象,在這裏重點說一下StringBuilder和StringBuffer基本相似,它們底層的構造器基本相同,不同的是StringBuffer是線程安全的,而StringBuilder則沒有實現線程安全功能的,正因爲這個原因,它的性能較好一些。。。
來點刺激的String的源碼:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    private static final long serialVersionUID = -6849794470754667710L;

    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];
    ...

可以看出String是由char[]來實現的,String是final類,也就意味着String類是不可以繼承的

注意:剛開始說String是不可以變的就是因爲char[]在是private的,並且String類沒有提供setter方法,導致無法改變這個String對象 String s = “123456”中的s是對象的引用,對象的引用指向對象。對象是不可變的,但是對象的引用是可變的。
下面分析看一下它們中的方法的源碼:
substring

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 substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

concat

 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);
    }

replace

public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }

可以發現subString、concat、replace操作都是不是在原字符串進行的,而是創建了一個新的字符串。也就是說,進行了上述操作後,本身的字符串並沒有改變。只是返回了一個新的對象的引用。。。
對String操作的任何改變都不會改變原對象,而任何改變String對象的操作都會產生新對象。
如:

String str1 = "java";
str1 = str1  + "struts";
str1 = str1 + "spring";

在這裏會產生五個字符串直接量不但有原先的3個還會產生額外兩個字符串直接量“javastruts”和“javastrutsspring”。程序中的str1依次指向3個不同的字符串對象。
因爲String是不可變的,所以會產生很多的臨時變量,在這裏就會選擇StringBuffer或者StringBuilder,因爲它們就會解決這個問題
StringBuffer、StringBuilder有兩個屬性:length和capacity。
其中length屬性表示其包含的字符序列的長度。與String對象length不同的是,StringBuffer、StringBuilder的length是可以改變的,可以通過length()、setLength(int len)方法來訪問和修改其字符串序列的長度,capacity屬性表示StringBuilder容量,capacity通常比length大,程序通常無須任何關心capacity屬性。

 public static void main(String[] args)
    {
        String str = "";
        for(int i = 0;i < 10000;i++)
            str += "Hello";
    }

通過反編譯可以看出每次循環一次都會new 一個StringBuilder對象,然後進行append操作,最後用toString方法返回String對象。試想一下如果這些對象沒有被JVM回收,則會造成多大的資源浪費。實際上jvm不傻,他會自動優化爲:

StringBuilder sb = new  StringBuilder(string);
sb.append("Hello");
str.toString();

再看一段代碼:

public static void main(String[] args)
    {
        StringBuilder sb = new StringBuilder();
        for(int i = 0;i < 10000;i++)
            sb.append("Hello");
    }

反編譯其字節碼文件,可以看出new操作只進行了一次,也就是說只生成一個對象,append操作是在原有對象上進行的,因此在循環10000次之後,資源消耗要小得多。
再講述一下StringBuffer,比StringBuilder多了一個關鍵字: synchronize。這個關鍵字在多線程操作的時起到安全保護的作用。

總結:如果在對字符串修改較少的情況下,建議使用String str = “Hello”;這種形式;如果在對字符串修改較多,則用StringBuilder;涉及到多線程,則用StringBuffer

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章