String,StringBuffer与StringBuilder的区别

(转自:http://www.cnblogs.com/Wilange/p/7570633.html

1.先来分析一下这三个类之间的关系(都是通过字符数组来实现的)

  乍一看它们都是用于处理字符串的java类,而且长得也都差不多,相信肯定有人会以为StringBuffer和StringBuilder都是继承自String这个类,即认为String类是其他两个类的超类。这种想法似乎很合理,但其实是不对的,事实上StringBuffer和StringBuilder确实是继承自某个类,但是这个类并不是String,至于是哪个类呢?我i们来看一下JDK源码:

StringBuffer类部分源码

StringBuilder类部分源码


String类部分源码

  看到这里,这三个类的关系基本清晰:StringBuffer和StringBuilder都继承自AbstractStringBuilder这个类,而AbstractStringBuilder和String都继承自Object这个类(Object是所有java类的超类)。所以这三个类之间的关系可以大致表示为:

 

关于AbstractStringBuilder这个类,本人只在JDK1.8中的java.lang包下找到了,而在JDK1.6和JDK1.7中均未找到,似乎是1.8版本新加上去的,各位看官可以试着找找。

2.String是不可变类,而StringBuffer, StringBuilder是可变类

  我们查看这三个类的源码,发现String类没有append()、delete()、insert()这三个成员方法,而StringBuffer和StringBuilder都有这些方法,这就很容易理解了(这里就不粘代码了,大家可以找源码看看)。所以我们可以归纳如下:

  String —— 字符串常量;

  StringBuffer —— 字符串变量;

  StringBuilder —— 字符串变量。

这里再补充一点:从源代码仔细追究下去,可以发现StringBuffer和StringBuilder中的append、delete、insert这几个成员方法都是通过System类的arraycopy方法来实现的,即将原数组复制到目标数组。至于System类的定义和用法,笔者将在之后的文章进行介绍。

3.再来说一下执行速度

  在执行速度上,String < StringBuffer < Stringbuilder 。

  3.1 String < StringBuffer

  这是因为String类是不可变的,即字符串常量,所以每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象。这就会对程序运行产生很大的影响,因为当内存中的无引用对象多了以后,JVM的GC进程就会进行垃圾回收,这个过程会耗费很长一段时间,因此经常改变内容的字符串最好不要用 String类的对象。而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。
  但是在某些特殊情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 运行速度是远要比 StringBuffer 快的:

  但是如果要拼接的字符串来自于不同的String对象的话,那结果就不一样了:

  这时候使用StringBuffer的运行速度更快,而这是我们编程时的大部分情况。

  3.2 StringBuffer < StringBuilder

4.线程安全与非安全

  StringBuffer是线程安全的,而StringBuilder是非线程安全的,至于原因我们依然可以从它们的源码中找到。

StringBuffer类的部分源码

public synchronized int length() {
        return count;
    }

    @Override
    public synchronized void ensureCapacity(int minimumCapacity) {
        super.ensureCapacity(minimumCapacity);
    }

    @Override
    public synchronized void trimToSize() {
        super.trimToSize();
    }

    @Override
    public synchronized void setLength(int newLength) {
        toStringCache = null;
        super.setLength(newLength);
    }

    @Override
    public synchronized char charAt(int index) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        return value[index];
    }

    @Override
    public synchronized int codePointAt(int index) {
        return super.codePointAt(index);
    }

    @Override
    public synchronized int codePointBefore(int index) {
        return super.codePointBefore(index);
    }

    @Override
    public synchronized int offsetByCodePoints(int index, int codePointOffset) {
        return super.offsetByCodePoints(index, codePointOffset);
    }

    @Override
    public synchronized void getChars(int srcBegin, int srcEnd, char[] dst,
                                      int dstBegin)
    {
        super.getChars(srcBegin, srcEnd, dst, dstBegin);
    }

    @Override
    public synchronized void setCharAt(int index, char ch) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        toStringCache = null;
        value[index] = ch;
    }

    @Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

StringBuilder类的部分源码

@Override
    public StringBuilder append(int i) {
        super.append(i);
        return this;
    }

    @Override
    public StringBuilder append(long lng) {
        super.append(lng);
        return this;
    }

    @Override
    public StringBuilder append(float f) {
        super.append(f);
        return this;
    }

    @Override
    public StringBuilder append(double d) {
        super.append(d);
        return this;
    }
    @Override
    public StringBuilder insert(int index, char[] str, int offset,
                                int len)
    {
        super.insert(index, str, offset, len);
        return this;
    }

    @Override
    public StringBuilder insert(int offset, Object obj) {
            super.insert(offset, obj);
            return this;
    }

我们可以发现StringBuffer类中的大部分成员方法都被synchronized关键字修饰,而StringBuilder类没有出现synchronized关键字;至于StringBuffer类中那些没有用synchronized修饰的成员方法,如insert()、indexOf()等,通过源码上的注释可以知道,它们是调用StringBuffer类的其他方法来实现同步的。注意:toString()方法也是被synchronized关键字修饰的。

至于synchronized关键字的使用范围及其作用,这里做了一下较为全面的总结:

一、修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;

  • 当两个并发线程访问同一个对象中的synchronized(this){}同步代码块时,同一时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块;
    package code;
    
    public class Thread0 implements Runnable{
        @Override
        public void run() {
            synchronized (this) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName()+" "+i);
                }
            }
        }
        public static void main(String[] args) {
            Thread0 target = new Thread0();
            Thread thA = new Thread(target,"Thread A");
            Thread thB = new Thread(target,"Thread B");
            thA.start();
            thB.start();
        }
    
    }

 

  • 当一个线程访问对象中的一个synchronized(this){}同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this){}同步代码块;
    package code;
    
    public class Thread0 implements Runnable{
        @Override
        public void run() {
            synchronized (this) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName()+" "+i);
                }
            }
            
            for (int j = 0; j < 5; j++) {
                System.out.println(Thread.currentThread().getName()+" "+j);
            }
            
        }
        public static void main(String[] args) {
            Thread0 target = new Thread0();
            Thread thA = new Thread(target,"Thread A");
            Thread thB = new Thread(target,"Thread B");
            thA.start();
            thB.start();
        }
    }

  • 当一个线程访问对象中的一个synchronized(this){}同步代码块时,其他线程对对象中所有其它synchronized(this){}同步代码块的访问将被阻塞。
    package code;
    
    public class Thread0 {
        public  void fun0() {
            synchronized (this){
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName()+" "+i);
                }
            }
        }
        
        public  void fun1() {
            synchronized (this){
                for (int j = 0; j < 5; j++) {
                    System.out.println(Thread.currentThread().getName()+" "+j);
                }
            }
        }
        
        public static void main(String[] args) {
            Thread0 target = new Thread0();
            Thread thA = new Thread(new Runnable() {
                public void run() {
                    target.fun0();
                }
            }, "Thread A");
            Thread thB = new Thread(new Runnable() {
                public void run() {
                    target.fun1();
                }
            },"Thread B");
            thA.start();
            thB.start();
        }
    }

二、修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;

  • synchronized修饰方法时,其作用和修饰代码块类似,此处不再赘述
    package code;
    
    public class Thread0 {
        public synchronized void fun0() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName()+" "+i);
                }
        }
        
        public synchronized void fun1() {
                for (int j = 0; j < 5; j++) {
                    System.out.println(Thread.currentThread().getName()+" "+j);
                }
        }
        
        public static void main(String[] args) {
            Thread0 target = new Thread0();
            Thread thA = new Thread(new Runnable() {
                public void run() {
                    target.fun0();
                }
            }, "Thread A");
            Thread thB = new Thread(new Runnable() {
                public void run() {
                    target.fun1();
                }
            },"Thread B");
            thA.start();
            thB.start();
        }
    }

 

三、修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;

  • 因为静态方法(或变量)是属于其所属类的,而不是属于该类的对象的,所以synchronized关键字修饰的静态方法锁定的是这个类的所有对象,即所有对象都是同一把锁。
    package code;
    
    public class MyThread {
        public synchronized static void function() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName()+" "+i);
                }
        }
    
        public static void main(String[] args) {
            MyThread target0 = new MyThread();
            MyThread target1 = new MyThread();
            
            Thread thA = new Thread(new Runnable() {
                public void run() {
                    target0.function();
                }
            }, "Thread A");
            
            Thread thB = new Thread(new Runnable() {
                public void run() {
                    target1.function();
                }
            }, "Thread B");
            thA.start();
            thB.start();
        }
    }

 

四、修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

  • synchronized关键字还可以用来修饰类,其作用与修饰静态方法类似,即所有类用的是同一把锁,用法如下:

package code;

public class MyThread {
    
    public  static void function() {
        synchronized(MyThread.class){
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName()+" "+i);
            }
        }
}
    
    public static void main(String[] args) {
        MyThread target0 = new MyThread();
        MyThread target1 = new MyThread();
        
        Thread thA = new Thread(new Runnable() {
            public void run() {
                target0.function();
            }
        }, "Thread A");
        
        Thread thB = new Thread(new Runnable() {
            public void run() {
                target1.function();
            }
        }, "Thread B");
        thA.start();
        thB.start();
    }
}

这里再来解释一下3.2节中留下的问题,在运行速度方面StringBuffer<StringBuilder,这是因为StringBuffer由于线程安全的特性,常常应用于多线程的程序中,为了保证多线程同步一些线程就会遇到阻塞的情况,这就使得StringBuffer的运行时间增加,从而使得运行速度减慢;而StringBuilder通常不会出现多线程的情况,所以运行时就不会被阻塞,运行速度也自然就比StringBuffer快了。

5.final关键字修饰

从第1节中展示的源码,我们可以很快得到结论:String, StringBuffer, StringBuilder都能够用final关键字修饰,此处不再过多解释。

但需要注意的是:

  •  final修饰的类不能被继承;
  •  final修饰的方法不能被继承类重写;
  •  final修饰的变量为常量,不能被改变。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页