java String和StringBuilder、StringBuffer之间的区别

java String和StringBuilder、StringBuffer之间的区别

可变与不可变

  • String类中使用字符数组保存字符串,如下就是,因为有“final”修饰符,所以可以知道string对象是不可变的。
private final char value[];

String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且大量浪费有限的内存空间。

String a = "a"; //假设a指向地址0x0001
a = "b";//重新赋值后a指向地址0x0002,但0x0001地址中保存的"a"依旧存在,但已经不再是a所指向的,a 已经指向了其它地址。

因此String的操作都是改变赋值地址而不是改变值操作

  • StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,如下就是,可知这两种对象都是可变的。
char[] value;

StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。 每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量。

StringBuffer buf=new StringBuffer(); //分配长16字节的字符缓冲区
StringBuffer buf=new StringBuffer(512); //分配长512字节的字符缓冲区
StringBuffer buf=new StringBuffer("this is a test")//在缓冲区中存放了字符串,并在后面预留了16字节的空缓冲区。 

StringBuffer和StringBuilder类功能基本相似,主要区别在于StringBuffer类的方法是多线程、安全的,而StringBuilder不是线程安全的,相比而言,StringBuilder类会略微快一点。对于经常要改变值的字符串应该使用StringBuffer和StringBuilder类。

是否多线程安全

String中的对象是不可变的,也就可以理解为常量,显然线程安全。

StringBuffer对方法加了同步锁,所以是线程安全的。看如下源码:

public synchronized StringBuffer reverse() {
    super.reverse();
    return this;
}

public synchronized int indexOf(String subString, int start) {
    return super.indexOf(subString, start);
}

StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

StringBuffer是线程安全的,这意味着它已经同步方法来控制访问,以便只有一个线程可以在同一时间访问一个StringBuffer对象同步代码。因此,StringBuffer的对象通常在多线程环境中是安全的,使用多个线程可以试图同时访问相同StringBuffer对象。

StringBuilder类非常相似的StringBuffer,不同之处在于它的访问不同步的,因此,它不是线程安全的。由于不同步,StringBuilder的性能可以比StringBuffer更好。因此,如果在单线程环境中工作,使用StringBuilder,而不使用StringBuffer可能会有更高的性能。这也类似其他情况,如StringBuilder的局部变量(即一个方法中的一个变量),其中只有一个线程会访问一个StringBuilder对象。

StringBuffer和StringBuilder类的速度比较

一般情况下,速度从快到慢:StringBuilder>StringBuffer>String,这种比较是相对的,不是绝对的。(要考虑程序是单线程还是多线程)

package com.hysum.test;

public class StringTest {
    final static int time = 50000; //循环次数 
    /*
     * String类测试方法
     */
    public void test(String s){
        long begin = System.currentTimeMillis();//获取当前系统时间(毫秒数),开始
        for(int i=0; i<time; i++){
        s += "add";
        }
        long over = System.currentTimeMillis();//获取当前系统时间(毫秒数),结束
        System.out.println("操作"+s.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
        }
    /*
     * StringBuffer类测试方法
     */
    public void test(StringBuffer s){
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
        s.append("add");
        }
        long over = System.currentTimeMillis();
        System.out.println("操作"+s.getClass().getCanonicalName()+"类型使用的时间为:"+(over-begin)+"毫秒");
        }
    /*
     * StringBuilder类测试方法
     */
    public void test(StringBuilder s){
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
        s.append("add");
        }
        long over = System.currentTimeMillis();
        System.out.println("操作"+s.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
        }

    /*对 String 直接进行字符串拼接的测试*/
    public void test2(){//操作字符串对象引用相加类型使用的时间
        String s2 = "abcd";
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
        String s = s2 + s2 +s2;
        }
        long over = System.currentTimeMillis();
        System.out.println("操作字符串对象引用相加类型使用的时间为:"+(over-begin)+"毫秒");
        }
    public void test3(){//操作字符串相加使用的时间
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
        String s = "abcd" + "abcd" +  "abcd";
        }
        long over = System.currentTimeMillis();
        System.out.println("操作字符串相加使用的时间为:"+(over-begin)+"毫秒");
        } 
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String s1 =  "abcd";
        StringBuffer st1 = new StringBuffer( "abcd");
        StringBuilder st2 = new StringBuilder( "abcd");
        StringTest tc = new StringTest();
        tc.test(s1);
        tc.test(st1);
        tc.test(st2);
        tc.test2();
        tc.test3(); 
    }

}

运行结果:
这里写图片描述

从上面的结果可以看出,不考虑多线程,采用String对象时,执行时间比其他两个都要高得多,而采用StringBuffer对象和采用StringBuilder对象的差别也比较明显;而以String类为例,操作字符串对象引用相加类型使用的时间比直接/操作字符串相加使用的时间也多得多。由此可见,如果我们的程序是在单线程下运行,或者是不必考虑到线程同步问题,我们应该优先使用StringBuilder类;如果要保证线程安全,自然是StringBuffer;能直接操作字符串不用字符串引用就直接操作字符串。

StringBuilder与StringBuffer共同点

StringBuilder与StringBuffer有公共父类AbstractStringBuilder(抽象类)。

StringBuilder、StringBuffer的方法都会调用AbstractStringBuilder中的公共方法,如super.append(…)。只是StringBuffer会在方法上加synchronized关键字,进行同步。

方法说明
StringBuffer append(参数)追加内容到当前StringBuffer对象的末尾,类似于字符串的连接
StringBuffer deleteCharAt(int index)删除指定位置的字符,然后将剩余的内容形成新的字符串
StringBuffer insert(位置, 参数)在StringBuffer对象中插入内容,然后形成新的字符串
StringBuffer reverse()将StringBuffer对象中的内容反转,然后形成新的字符串
void setCharAt(int index, char ch)修改对象中索引值为index位置的字符为新的字符ch
void trimToSize()将StringBuffer对象的中存储空间缩小到和字符串长度一样的长度,减少空间的浪费,和String的trim()是一样的作用
StringBuffer delete(int start, int end)删除指定区域的字符串
StringBuffer replace(int start, int end, String s)用新的字符串替换指定区域的字符串
void setlength(int n)设置字符串缓冲区大小
int capacity()获取字符串的容量
void ensureCapacity(int n)确保容量至少等于指定的最小值。如果当前容量小于该参数,然后分配一个新的内部数组容量更大。新的容量是较大的.
getChars(int start,int end,char chars[],int charStart);将字符串的子字符串复制给数组

以下是各个方法的代码示例:

public static void main(String[] args) {
        // TODO Auto-generated method stub
        StringBuilder str=new StringBuilder("学习 java 编程");

        //增加字符串内容的方法
        //append(参数),追加内容到当前对象的末尾
        str.append("学习使我快乐");
        System.out.println("追加内容到当前对象的末尾:"+str);
        // insert(位置, 参数),在对象中插入内容
        str.insert(10,',');
        System.out.println("在对象中插入内容:"+str);

        //操作字符串内容的方法
        //delete(int start, int end),删除指定区域的字符串
        str.delete(11, 17);
        System.out.println("删除指定区域的字符串:"+str);
        //deleteCharAt(int index),删除指定位置的字符
        str.deleteCharAt(10);
        System.out.println("删除指定位置的字符:"+str);
        //setCharAt(int index, char newChar),修改对象中索引值为index位置的字符为新的字符ch
        str.setCharAt(3, 'J');
        System.out.println("修改对象中索引值为index位置的字符为新的字符ch:"+str);
        //replace(int start, int end, String s), 用新的字符串替换指定区域的字符串
        str.replace(4, 7, "AVA");
        System.out.println("用新的字符串替换指定区域的字符串:"+str);
        // reverse()内容反转
        str.reverse();
        System.out.println("内容反转:"+str);
        //将字符串的子字符串复制给数组。
        char[] ch  = new char[5];
        str.getChars(0, 4, ch, 0);
        System.out.println("将字符串的子字符串复制给数组:"+Arrays.toString(ch));




        StringBuilder str2=new StringBuilder(30);//创建一个长度为30的字符串
        str2.append("JAVA");
        System.out.println("字符串长度为:"+str2.length());//length(),获取字符串长度
        System.out.println("字符串容量为:"+str2.capacity());//capacity(),获取字符串的容量
        //有关字符串空间的方法
        //setLength(int newSize),设置字符串缓冲区大小
        str2.setLength(20);
        System.out.println("字符串长度为:"+str2.length());
        System.out.println("字符串容量为:"+str2.capacity());
        //ensureCapacity(int n),重新设置字符串容量的大小
        str2.ensureCapacity(20);
        System.out.println("字符串长度为:"+str2.length());
        System.out.println("字符串容量为:"+str2.capacity());
        str2.ensureCapacity(35);
        System.out.println("字符串长度为:"+str2.length());
        System.out.println("字符串容量为:"+str2.capacity());
        //trimToSize(),存储空间缩小到和字符串长度一样的长度
        str2.trimToSize();
        System.out.println("字符串长度为:"+str2.length());
        System.out.println("字符串容量为:"+str2.capacity());    
    }
}

运行结果:
这里写图片描述

结果分析:

  1. 在使用有范围的参数方法时,要注意范围包括开头不包括结尾!
  2. insert方法的位置是你要插入的位置,不是插入前一个位置!
  3. getChars方法中注意字符数组的长度一定要大于等于begin到end之间字符的长度!
  4. length是字符串内容的长度,而capacity是字符串容量(包括缓存区)的长度!
  5. ensureCapacity方法是确保容量至少等于指定的最小值。如果当前容量小于该参数,然后分配一个新的内部数组容量更大(不是你指定的值,系统自动分配一个空间)。如果当前容量不小于该参数,则容量不变。
  6. trimToSize(),存储空间缩小到和字符串长度一样的长度。避免空间的浪费!

总结

  • 如果要操作少量的数据用 = String
  • 单线程操作字符串缓冲区下操作大量数据 = StringBuilder
  • 多线程操作字符串缓冲区下操作大量数据 = StringBuffer
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值