String,StringBuffer,StringBuilder的区别这个问题几乎是面试必问的题,这里做了一些总结:
乍一看它们都是用于处理字符串的java类,而且长得也都差不多,相信肯定有人会以为StringBuffer和StringBuilder都是继承自String这个类,即认为String类是其他两个类的超类。这种想法似乎很合理,但其实是不对的,事实上StringBuffer和StringBuilder确实是继承自某个类,但是这个类并不是String,至于是哪个类呢?我i们来看一下JDK源码:
StringBuffer类部分源码
StringBuilder类部分源码
看到这里,这三个类的关系基本清晰:StringBuffer和StringBuilder都继承自AbstractStringBuilder这个类,而AbstractStringBuilder和String都继承自Object这个类(Object是所有java类的超类)。所以这三个类之间的关系可以大致表示为:
关于AbstractStringBuilder这个类,本人只在JDK1.8中的java.lang包下找到了,而在JDK1.6和JDK1.7中均未找到,似乎是1.8版本新加上去的,各位看官可以试着找找。
我们查看这三个类的源码,发现String类没有append()、delete()、insert()这三个成员方法,而StringBuffer和StringBuilder都有这些方法,这就很容易理解了(这里就不粘代码了,大家可以找源码看看)。所以我们可以归纳如下:
String —— 字符串常量;
StringBuffer —— 字符串变量;
StringBuilder —— 字符串变量。
这里再补充一点:从源代码仔细追究下去,可以发现StringBuffer和StringBuilder中的append、delete、insert这几个成员方法都是通过System类的arraycopy方法来实现的,即将原数组复制到目标数组。至于System类的定义和用法,笔者将在之后的文章进行介绍。
在执行速度上,String < StringBuffer < Stringbuilder 。
这是因为String类是不可变的,即字符串常量,所以每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象。这就会对程序运行产生很大的影响,因为当内存中的无引用对象多了以后,JVM的GC进程就会进行垃圾回收,这个过程会耗费很长一段时间,因此经常改变内容的字符串最好不要用 String类的对象。而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。
但是在某些特殊情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 运行速度是远要比 StringBuffer 快的:
但是如果要拼接的字符串来自于不同的String对象的话,那结果就不一样了:
这时候使用StringBuffer的运行速度更快,而这是我们编程时的大部分情况。
StringBuffer是线程安全的,而StringBuilder是非线程安全的,至于原因我们依然可以从它们的源码中找到。
StringBuffer类的部分源码
1 public synchronized int length() { 2 return count; 3 } 4 5 @Override 6 public synchronized void ensureCapacity(int minimumCapacity) { 7 super.ensureCapacity(minimumCapacity); 8 } 9 10 @Override11 public synchronized void trimToSize() {12 super.trimToSize();13 }14 15 @Override16 public synchronized void setLength(int newLength) {17 toStringCache = null;18 super.setLength(newLength);19 }20 21 @Override22 public synchronized char charAt(int index) {23 if ((index < 0) || (index >= count))24 throw new StringIndexOutOfBoundsException(index);25 return value[index];26 }27 28 @Override29 public synchronized int codePointAt(int index) {30 return super.codePointAt(index);31 }32 33 @Override34 public synchronized int codePointBefore(int index) {35 return super.codePointBefore(index);36 }37 38 @Override39 public synchronized int offsetByCodePoints(int index, int codePointOffset) {40 return super.offsetByCodePoints(index, codePointOffset);41 }42 43 @Override44 public synchronized void getChars(int srcBegin, int srcEnd, char[] dst,45 int dstBegin)46 {47 super.getChars(srcBegin, srcEnd, dst, dstBegin);48 }49 50 @Override51 public synchronized void setCharAt(int index, char ch) {52 if ((index < 0) || (index >= count))53 throw new StringIndexOutOfBoundsException(index);54 toStringCache = null;55 value[index] = ch;56 }57 58 @Override59 public synchronized StringBuffer append(Object obj) {60 toStringCache = null;61 super.append(String.valueOf(obj));62 return this;63 }64 65 @Override66 public synchronized StringBuffer append(String str) {67 toStringCache = null;68 super.append(str);69 return this;70 }
StringBuilder类的部分源码
1 @Override 2 public StringBuilder append(int i) { 3 super.append(i); 4 return this; 5 } 6 7 @Override 8 public StringBuilder append(long lng) { 9 super.append(lng);10 return this;11 }12 13 @Override14 public StringBuilder append(float f) {15 super.append(f);16 return this;17 }18 19 @Override20 public StringBuilder append(double d) {21 super.append(d);22 return this;23 }24 @Override25 public StringBuilder insert(int index, char[] str, int offset,26 int len)27 {28 super.insert(index, str, offset, len);29 return this;30 }31 32 @Override33 public StringBuilder insert(int offset, Object obj) {34 super.insert(offset, obj);35 return this;36 }
我们可以发现StringBuffer类中的大部分成员方法都被synchronized关键字修饰,而StringBuilder类没有出现synchronized关键字;至于StringBuffer类中那些没有用synchronized修饰的成员方法,如insert()、indexOf()等,通过源码上的注释可以知道,它们是调用StringBuffer类的其他方法来实现同步的。注意:toString()方法也是被synchronized关键字修饰的。
至于synchronized关键字的使用范围及其作用,这里做了一下较为全面的总结:
一、修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
1 package code; 2 3 public class Thread0 implements Runnable{ 4 @Override 5 public void run() { 6 synchronized (this) { 7 for (int i = 0; i < 5; i++) { 8 System.out.println(Thread.currentThread().getName()+" "+i); 9 }10 }11 }12 public static void main(String[] args) {13 Thread0 target = new Thread0();14 Thread thA = new Thread(target,"Thread A");15 Thread thB = new Thread(target,"Thread B");16 thA.start();17 thB.start();18 }19 20 }
1 package code; 2 3 public class Thread0 implements Runnable{ 4 @Override 5 public void run() { 6 synchronized (this) { 7 for (int i = 0; i < 5; i++) { 8 System.out.println(Thread.currentThread().getName()+" "+i); 9 }10 }11 12 for (int j = 0; j < 5; j++) {13 System.out.println(Thread.currentThread().getName()+" "+j);14 }15 16 }17 public static void main(String[] args) {18 Thread0 target = new Thread0();19 Thread thA = new Thread(target,"Thread A");20 Thread thB = new Thread(target,"Thread B");21 thA.start();22 thB.start();23 }24 }
1 package code; 2 3 public class Thread0 { 4 public void fun0() { 5 synchronized (this){ 6 for (int i = 0; i < 5; i++) { 7 System.out.println(Thread.currentThread().getName()+" "+i); 8 } 9 }10 }11 12 public void fun1() {13 synchronized (this){14 for (int j = 0; j < 5; j++) {15 System.out.println(Thread.currentThread().getName()+" "+j);16 }17 }18 }19 20 public static void main(String[] args) {21 Thread0 target = new Thread0();22 Thread thA = new Thread(new Runnable() {23 public void run() {24 target.fun0();25 }26 }, "Thread A");27 Thread thB = new Thread(new Runnable() {28 public void run() {29 target.fun1();30 }31 },"Thread B");32 thA.start();33 thB.start();34 }35 }
二、修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
1 package code; 2 3 public class Thread0 { 4 public synchronized void fun0() { 5 for (int i = 0; i < 5; i++) { 6 System.out.println(Thread.currentThread().getName()+" "+i); 7 } 8 } 9 10 public synchronized void fun1() {11 for (int j = 0; j < 5; j++) {12 System.out.println(Thread.currentThread().getName()+" "+j);13 }14 }15 16 public static void main(String[] args) {17 Thread0 target = new Thread0();18 Thread thA = new Thread(new Runnable() {19 public void run() {20 target.fun0();21 }22 }, "Thread A");23 Thread thB = new Thread(new Runnable() {24 public void run() {25 target.fun1();26 }27 },"Thread B");28 thA.start();29 thB.start();30 }31 }
三、修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
1 package code; 2 3 public class MyThread { 4 public synchronized static void function() { 5 for (int i = 0; i < 5; i++) { 6 System.out.println(Thread.currentThread().getName()+" "+i); 7 } 8 } 9 10 public static void main(String[] args) {11 MyThread target0 = new MyThread();12 MyThread target1 = new MyThread();13 14 Thread thA = new Thread(new Runnable() {15 public void run() {16 target0.function();17 }18 }, "Thread A");19 20 Thread thB = new Thread(new Runnable() {21 public void run() {22 target1.function();23 }24 }, "Thread B");25 thA.start();26 thB.start();27 }28 }
四、修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
1 package code; 2 3 public class MyThread { 4 5 public static void function() { 6 synchronized(MyThread.class){ 7 for (int i = 0; i < 5; i++) { 8 System.out.println(Thread.currentThread().getName()+" "+i); 9 }10 }11 }12 13 public static void main(String[] args) {14 MyThread target0 = new MyThread();15 MyThread target1 = new MyThread();16 17 Thread thA = new Thread(new Runnable() {18 public void run() {19 target0.function();20 }21 }, "Thread A");22 23 Thread thB = new Thread(new Runnable() {24 public void run() {25 target1.function();26 }27 }, "Thread B");28 thA.start();29 thB.start();30 }31 }
这里再来解释一下3.2节中留下的问题,在运行速度方面StringBuffer<StringBuilder,这是因为StringBuffer由于线程安全的特性,常常应用于多线程的程序中,为了保证多线程同步一些线程就会遇到阻塞的情况,这就使得StringBuffer的运行时间增加,从而使得运行速度减慢;而StringBuilder通常不会出现多线程的情况,所以运行时就不会被阻塞,运行速度也自然就比StringBuffer快了。
从第1节中展示的源码,我们可以很快得到结论:String, StringBuffer, StringBuilder都能够用final关键字修饰,此处不再过多解释。
但需要注意的是:
联系客服