浅谈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

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