三个字符串类String、StringBuilder、StringBuffer的区别与比较

来源:互联网 发布:mac excel 规划求解 编辑:程序博客网 时间:2024/05/17 07:48

一、前言
在编写JAVA代码时,我们经常会对字符串进行操作。那么在对字符串进行操作时,我们就会面临到String、StringBuilder、StringBuffer的使用和选择。下面就让我们来了解一下这三个字符串的实现方式的异同点吧!

二、简介
1.String:字符串,实现了Serializable序列化、Comparable比较器以及CharSequence字符序列接口,其底层结构是一个字符数组,被final修饰,表示不可变,即当它的值发生变化时,就会生成一个新的String对象;
2.StringBuilder:可变的字符序列,继承了AbstractStringBuilder类,同时实现了Serializable序列化以及CharSquence字符序列接口。其底层是一个长度可变的字符数组,并且它的方法没有实现synchronized同步锁,是不同步的。
3.StringBuffer:线程安全的可变字符序列,继承了AbstractStringBuilder类,同时实现了Serializable序列化以及CharSquence字符序列接口。底层也是长度可变的字符数组,方法实现了synchronized同步锁,是同步的,线程安全的。

三、三者的区别及选择
首先我们来看一下这三者的源代码的异同点:
下面是String类的部分源代码及伪代码:

    private final char value[];  //不可变的字符数组    private int hash;  //哈希值:默认为0    //构造方法    public String() {        this.value = new char[0]; //底层为字符数组    }    public String(String original) {        this.value = original.value;        this.hash = original.hash;    }    public String(char value[]) {        this.value = Arrays.copyOf(value, value.length);    }    public String(char value[], int offset, int count) {    }    ...    ...    //返回字符串长度    public int length() {        return value.length;    }    //判断是否为空    public boolean isEmpty() {        return value.length == 0;    }    //返回字符串索引处的字符的值    public char charAt(int index) {    }    //比较字符串与传入对象是否相等    public boolean equals(Object anObject) {    }    //两个String相比较,忽略大小写    public boolean equalsIgnoreCase(String anotherString) {    }    ...    ...
对应的StringBuilder的部分源代码及伪代码如下:
    //构造方法(默认生成一个长度为16的空字符数组)    public StringBuilder() {        super(16);    }    //自定义指定初始长度的字符数组    public StringBuilder(int capacity) {        super(capacity);    }    //构造一个字符数组,长度为传入字符串长度再加16    public StringBuilder(String str) {        super(str.length() + 16);        append(str);    }    public StringBuilder(CharSequence seq) {        this(seq.length() + 16);        append(seq);    }    //调用父类的方法,往字符数组中添加字符串    public StringBuilder append(String str) {        super.append(str);        return this;    }    //同时,这里给出父类AbstractStringBuilder的append方法    public AbstractStringBuilder append(String str) {        if (str == null) str = "null";        int len = str.length();        ensureCapacityInternal(count + len);        str.getChars(0, len, value, count);//将str字符串复制到value字符数组中(value数组是AbstractStringBuilder类的属性)        count += len;        return this;    }    //添加字符序列    private StringBuilder append(StringBuilder sb) {        if (sb == null)            return append("null");        int len = sb.length();        int newcount = count + len;        if (newcount > value.length)            expandCapacity(newcount);  //扩大字符数组的长度(默认扩大原本大小的两倍再加2,若仍小于newcount的长度,则取newcount的值为新的value的长度)        sb.getChars(0, len, value, count);        count = newcount;        return this;    }
StringBuffer的部分源代码以及伪代码如下:
    //构造方法,默认长度也为16    public StringBuffer() {        super(16);    }    public StringBuffer(int capacity) {        super(capacity);    }    public StringBuffer(String str) {        super(str.length() + 16);        append(str);    }    public StringBuffer(CharSequence seq) {        this(seq.length() + 16);        append(seq);    }    //返回字符串长度,同步方法    public synchronized int length() {        return count;    }    public synchronized int capacity() {        return value.length;    }    public synchronized StringBuffer append(String str) {        super.append(str);        return this;    }    public synchronized StringBuffer append(StringBuffer sb) {        super.append(sb);        return this;    }    //此方法本身没加同步锁,但在return时调用的方法具有同步锁机制    public StringBuffer append(CharSequence s) {        if (s == null)            s = "null";        if (s instanceof String)            return this.append((String)s);        if (s instanceof StringBuffer)            return this.append((StringBuffer)s);        return this.append(s, 0, s.length());    }

从上面StringBuffer和StringBuilder的源代码很明显可以看出,其实StringBuffer就是差不多在StringBuilder的方法的基础上都加上了同步锁,实现了线程安全,但是很明显的会在一定程度上降低效率。

那么,如果我们要对字符串进行操作时,比如较多的字符串拼接的时候,从效率的角度让我们来测试一下这三个都能实现字符串功能的类的各自效率如何,下面是测试代码,首先我们让这三个代码都经过10000次的进行“abcdefghijklmnopqrstuvwxyz”字符串的拼接,通过测算其运行时间来判断各自的效率:

        //测试String        long startTime1 = System.currentTimeMillis();        String text = null;        for(int i=0;i<10000;i++){            text +="abcdefghijklmnopqrstuvwxyz";        }        System.out.println("一万次String循环时间:"+(System.currentTimeMillis()-startTime1)+"毫秒");        //测试StringBuffer        long startTime2 = System.currentTimeMillis();        StringBuffer textBuffer = new StringBuffer();        for(int i=0;i<10000;i++){            textBuffer.append("abcdefghijklmnopqrstuvwxyz");        }        System.out.println("一万次StringBuffer循环时间:"+(System.currentTimeMillis()-startTime2)+"毫秒");        //测试StringBuilder        long startTime3 = System.currentTimeMillis();        StringBuilder textBuilder = new StringBuilder();        for(int i=0;i<10000;i++){            textBuilder.append("abcdefghijklmnopqrstuvwxyz");        }        System.out.println("一万次StringBuilder循环时间:"+(System.currentTimeMillis()-startTime3)+"毫秒");

测试结果:
一万次String循环时间:4515毫秒
一万次StringBuffer循环时间:1毫秒
一万次StringBuilder循环时间:1毫秒

我们可以看到很明显的用String测试出来的结果会比其他两个时间花费的要多得多,同时占用的系统资源也要多得多。因为String是一个final常量,不能被改变,所以在进行字符串拼接时,会不停的new对象出来,每拼接一次就要多new出来一个对象,自然而然地会使得效率变低,系统资源占用变大。
而对于上一个测试结果显示StringBuffer和StringBuilder比较的消耗时间不是很明显,对此我们针对上一次测试增加其循环次数为一百万次得到如下结果:

测试结果:
一百万次StringBuffer循环时间:154毫秒
一百万次StringBuilder循环时间:92毫秒

得到的测试结果显示在一个线程的条件下其实只有当字符串拼接的量达到非常非常多的情况时,StringBuffer和StringBuilder才会有比较明显的效率差距,但其实差距也并不大。
所以在通常情况下我们在使用局部变量或者单线程的条件下StringBuilder和StringBuffer不会有特别明显的效率差距,当然最好使用StringBuilder提高效率降低资源占用率。当然在多线程或者说用作全局变量时,则必须使用StringBuffer才能保证线程安全。而String则比较适用于较少的数据操作的情况。

1 0
原创粉丝点击