JAVA的String、StringBuilder和StringBuffer类的区别

StringBuffer类(或者StringBuilder)和String一样,也用来代表字符串,只是由于StringBuffer的内部实现方式和String不同,所以StringBuffer在进行字符串处理时,不生成新的对象,在内存使用上要优于String类。

所以在实际使用时,如果经常需要对一个字符串进行修改,例如插入、删除等操作,使用StringBuffer要更加适合一些。

在StringBuffer类中存在很多和String类一样的方法,这些方法在功能上和String类中的功能是完全一样的。

但是有一个最显著的区别在于,对于StringBuffer对象的每次修改都会改变对象自身,这点是和String类最大的区别。

另外由于StringBuffer是线程安全的,关于线程的概念后续有专门的章节进行介绍,所以在多线程程序中也可以很方便的进行使用,但是程序的执行效率相对来说就要稍微慢一些。

1、StringBuffer对象的初始化

StringBuffer对象的初始化不像String类的初始化一样,Java提供的有特殊的语法,而通常情况下一般使用构造方法进行初始化。
例如:

     StringBuffer s = new StringBuffer();

这样初始化出的StringBuffer对象是一个空的对象。
如果需要创建带有内容的StringBuffer对象,则可以使用:
StringBuffer s = new StringBuffer(“abc”);
这样初始化出的StringBuffer对象的内容就是字符串”abc”。
需要注意的是,StringBuffer和String属于不同的类型,也不能直接进行强制类型转换,下面的代码都是错误的:

StringBuffer s = “abc”;        //赋值类型不匹配
tringBuffer s = (StringBuffer)”abc”; //不存在继承关系,无法进行强转
StringBuffer对象和String对象之间的互转的代码如下:
         String s = “abc”;
         StringBuffer sb1 = new StringBuffer(“123”);
         StringBuffer sb2 = new StringBuffer(s);   //String转换为StringBuffer
         String s1 = sb1.toString();              //StringBuffer转换为String

2、StringBuffer的常用方法

StringBuffer类中的方法主要偏重于对于字符串的变化,例如追加、插入和删除等,这个也是StringBuffer和String类的主要区别。
a、append方法
public StringBuffer append(boolean b)
该方法的作用是追加内容到当前StringBuffer对象的末尾,类似于字符串的连接。调用该方法以后,StringBuffer对象的内容也发生改变,例如:
StringBuffer sb = new StringBuffer(“abc”);
sb.append(true);
则对象sb的值将变成”abctrue”。
使用该方法进行字符串的连接,将比String更加节约内容,例如应用于数据库SQL语句的连接,例如:

StringBuffer sb = new StringBuffer();
String user = “test”;
String pwd = “123”;
sb.append(“select * from userInfo where username=“)
          .append(user)
          .append(“ and pwd=”)
          .append(pwd);

这样对象sb的值就是字符串“select * from userInfo where username=test and pwd=123”。

b、deleteCharAt方法
public StringBuffer deleteCharAt(int index)
该方法的作用是删除指定位置的字符,然后将剩余的内容形成新的字符串。例如:

StringBuffer sb = new StringBuffer(“Test”);
sb. deleteCharAt(1);

该代码的作用删除字符串对象sb中索引值为1的字符,也就是删除第二个字符,剩余的内容组成一个新的字符串。所以对象sb的值变为”Tst”。
还存在一个功能类似的delete方法:
public StringBuffer delete(int start,int end)
该方法的作用是删除指定区间以内的所有字符,包含start,不包含end索引值的区间。例如:

StringBuffer sb = new StringBuffer(“TestString”);
sb. delete (1,4);

该代码的作用是删除索引值1(包括)到索引值4(不包括)之间的所有字符,剩余的字符形成新的字符串。则对象sb的值是”TString”。
c、insert方法
public StringBuffer insert(int offset, boolean b)
该方法的作用是在StringBuffer对象中插入内容,然后形成新的字符串。例如:

StringBuffer sb = new StringBuffer(“TestString”);
sb.insert(4,false);

该示例代码的作用是在对象sb的索引值4的位置插入false值,形成新的字符串,则执行以后对象sb的值是”TestfalseString”。
d、reverse方法

public StringBuffer reverse()

该方法的作用是将StringBuffer对象中的内容反转,然后形成新的字符串。例如:

StringBuffer sb = new StringBuffer(“abc”);
sb.reverse();

转以后,对象sb中的内容将变为”cba”。
e、setCharAt方法

public void setCharAt(int index, char ch)

该方法的作用是修改对象中索引值为index位置的字符为新的字符ch。例如:
StringBuffer sb = new StringBuffer(“abc”);
sb.setCharAt(1,’D’);
则对象sb的值将变成”aDc”。

f、trimToSize方法
public void trimToSize()

该方法的作用是将StringBuffer对象的中存储空间缩小到和字符串长度一样的长度,减少空间的浪费。
总之,在实际使用时,String和StringBuffer各有优势和不足,可以根据具体的使用环境,选择对应的类型进行使用。

String和StringBuffer的效率对比

为了更加明显地看出它们的执行效率,下面的代码,将26个英文字母加了10000次。

public class Demo {
    public static void main(String[] args){
        String fragment = "abcdefghijklmnopqrstuvwxyz";
        int times = 10000;
    // 通过String对象
        long timeStart1 = System.currentTimeMillis();
        String str1 = "";
        for (int i=0; i<times; i++) {
        str1 += fragment;
        }
        long timeEnd1 = System.currentTimeMillis();
        System.out.println("String: " + (timeEnd1 - timeStart1) +     "ms");
    // 通过StringBuffer
        long timeStart2 = System.currentTimeMillis();
        StringBuffer str2 = new StringBuffer();
        for (int i=0; i<times; i++) {
            str2.append(fragment);
        }
        long timeEnd2 = System.currentTimeMillis();
        System.out.println("StringBuffer: " + (timeEnd2 - timeStart2) + "ms");
    }
}

运行结果:

String: 5287ms
StringBuffer: 3ms

结论很明显,StringBuffer的执行效率比String快上千倍,这个差异随着叠加次数的增加越来越明显,当叠加次数达到30000次的时候,运行结果为:
String: 35923ms
StringBuffer: 8ms

所以,强烈建议在涉及大量字符串操作时使用StringBuffer。

StringBuilder类

StringBuilder类和StringBuffer类功能基本相似,方法也差不多,主要区别在于StringBuffer类的方法是多线程安全的,而StringBuilder不是线程安全的,相比而言,StringBuilder类会略微快一点。

StringBuffer、StringBuilder、String中都实现了CharSequence接口。

CharSequence是一个定义字符串操作的接口,它只包括length()、charAt(int index)、subSequence(int start, int end) 这几个API。

StringBuffer、StringBuilder、String对CharSequence接口的实现过程不一样,如下图所示:
这里写图片描述
可见,String直接实现了CharSequence接口;StringBuilder 和 StringBuffer都是可变的字符序列,它们都继承于AbstractStringBuilder,实现了CharSequence接口。

jdk的实现中StringBuffer与StringBuilder都继承自AbstractStringBuilder,对于多线程的安全与非安全看到StringBuffer中方法前面的一堆synchronized就大概了解了。

这里写图片描述

这里写图片描述

我们知道使用StringBuffer等无非就是为了提高java中字符串连接的效率,因为直接使用+进行字符串连接的话,jvm会创建多个String对象,因此造成一定的开销。
AbstractStringBuilder中采用一个char数组来保存需要append的字符串,char数组有一个初始大小,当append的字符串长度超过当前char数组容量时,则对char数组进行动态扩展,也即重新申请一段更大的内存空间,然后将当前char数组拷贝到新的位置,因为重新分配内存并拷贝的开销比较大,所以每次重新申请内存空间都是采用申请大于当前需要的内存空间的方式,这里是2倍。

分类总结如下:
(1)StringBuffer在进行追加操作的时候,只是在字符串的后边进行追加操作,追加的过程也是需要进行判断预存数组大小是否超过一开始设定的16子字符,然后才可以进行追加,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。;由于StringBuilder和StringBuffer本身都是继承自AbstractStringBuilder,在进行追加操作的时候,使用的就是AbstractStringBuilder类中的append方法,下图中展示了AbstractStringBuilder类中所有重载的append方法,根据不同的需求可以实现不同的功能,
这里写图片描述

以append(CharSequence s)为例,展示一下:(下边的方法都是AbstractStringBuilder中的方法)

@Override
    public AbstractStringBuilder append(CharSequence s) {
        if (s == null)
            return appendNull();
        if (s instanceof String)
            return this.append((String)s);
        if (s instanceof AbstractStringBuilder)
            return this.append((AbstractStringBuilder)s);

        return this.append(s, 0, s.length());
    }

首先进行判断是否为空,然后判断是哪一个实例,最后掉用append另一个重载的方法:

@Override
    public AbstractStringBuilder append(CharSequence s, int start, int end) {
        if (s == null)
            s = "null";
        if ((start < 0) || (start > end) || (end > s.length()))
            throw new IndexOutOfBoundsException(
                "start " + start + ", end " + end + ", s.length() "
                + s.length());
        int len = end - start;
        ensureCapacityInternal(count + len);
        for (int i = start, j = count; i < end; i++, j++)
            value[j] = s.charAt(i);
        count += len;
        return this;
    }

其中:

    /**
     * The count is the number of characters used.
     */
    int count;

    /**
     * The value is used for character storage.
     */
    char[] value;

可以看出:ensureCapacityInternal(count + len); 是一个扩容的过程,value[j] = s.charAt(i); 是保存追加字符串的过程,具体的过程就是再原StringBuffer对象的基础上首先通过扩容,之后在进行追加操作;

(2)StringBuffer的四个构造方法如下:

StringBuffer() 
构造一个其中不带字符的字符串缓冲区,其初始容量为 16 个字符。

StringBuffer(CharSequence seq)  
public java.lang.StringBuilder(CharSequence seq) 构造一个字符串缓冲区,它包含与指定的 CharSequence 相同的字符。

StringBuffer(int capacity)   
构造一个不带字符,但具有指定初始容量的字符串缓冲区。

StringBuffer(String str)  
构造一个字符串缓冲区,并将其内容初始化为指定的字符串内容

每个字符串缓冲区都有一定的容量。只要字符串缓冲区所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区数组。如果内部缓冲区溢出,则此容量自动增大。从 JDK 5 开始,为该类补充了一个单个线程使用的等价类,即 StringBuilder。与该类相比,通常应该优先使用 StringBuilder 类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。

(3)而对于String来说,虽然追加(字符串串联)的本质也是通过创建StringBuilder对象,通过StringBuilder的append方法来实现的,但是每次追加的过程都是一次创建StringBuilder对象的过程,因也就是说在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,效率显然要比直接使用StringBuilder来说要低很多,网上也有很多测试的实验,也充分说明这一点,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的;

(4)而在某些特别情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的:

String S1 = “This is only a” + “ simple” + “ test”;
StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);

会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,在JVM 眼里,这个String S1 = “This is only a” + “ simple” + “test”;
其实就是: String S1 = “This is only a simple test”;
所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如:

String S2 = “This is only a”;
String S3 = “ simple”;
String S4 = “ test”;
String S1 = S2 +S3 + S4;

这时候 JVM 会规规矩矩的按照原来的方式去做, S1 对象的生成速度就不像刚才那么快了,一会儿我们可以来个测试作个验证。
由此我们得到第一步结论: 在大部分情况下 StringBuffer > String

(4)总的来说,StringBuilder适合于需要进行经常性的字符追加删除操作等,String本身设计的初衷应该是为了在后边的使用过程种,不会进行太多的增删操作,毕竟String是final类型的,是不可变的对象,因此他们都有所擅长的地方,根据自己的需求进行合理的选择即可;

总结

线程安全:
StringBuffer:线程安全
StringBuilder:线程不安全

速度:
一般情况下,速度从快到慢为 StringBuilder > StringBuffer > String,当然这是相对的,不是绝对的。

使用环境:
操作少量的数据使用 String;
单线程操作大量数据使用 StringBuilder;
多线程操作大量数据使用 StringBuffer。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页