第九章《字符串》第4节:StringBuffer和StringBuilder

时间:2022-12-31 16:55:31

​前面几个小节主要讲解了String类的特点以及使用方法。实际上,Java语言中并不只有String类能表示字符串,StringBuffer和StringBuilder也可以表示字符串,它们共同构成了字符串家族。String、StringBuffer和StringBuilder这三个类都实现了CharSequence接口。CharSequence是两个单词的组合,其意义为“字符序列”。CharSequence接口中定义了很多操作字符串的方法,有些是抽象方法,有些是已经实现了的方法,其中String类在CharSequence接口的基础上扩展出的方法最多,因此String类与其他两个类相比功能更强大,并且程序员在大多数情况下也都会选择用String类来表示字符串。

为什么已经有了强大String类,还要定义出StringBuffer和StringBuilder这两个类来表示字符串呢?就是因为String是一个不可变类,这种不可变性导致了每次对字符串进行修改都会产生一个新的String对象。有时候,程序员需要连续的对字符串进行修改,这样就会导致新产生的String对象消耗了大量的内存空间。为了解决这个问题,Java语言又定义出了StringBuffer和StringBuilder这两个类来解决这个问题。StringBuffer和StringBuilder是可变类,程序员可以直接对它们的值进行修改,修改值之后不会产生任何新的对象。​

String、StringBuffer和StringBuilder这三个类中都定义了一个名叫value的byte型的数组,这个数组用于存储字符序列。为什么这三个类存储字符序列的原理相同,但String是不可变类,而StringBuffer和StringBuilder却是可变类呢?这是因为每次对字符串进行修改时,String类不会改变原有value数组的内容,而是会新创建一个数组来存储字符序列,并且以这个数组为基础创建一个新的String对象,而StringBuffer和StringBuilder则不同,每次修改字符串时,都直接在原有的value数组上进行操作,不会创建出新的对象。下面的【例09_16】展示了三种不同的字符串类在修改值之后是否产生新的对象。​

【例09_16 在字符串末尾添加数据】

Exam09_16.java​

public class Exam09_16 {
public static void main(String[] args) {
String str1 = "abc";
String str2 = str1.concat("xyz");

StringBuffer buf1 = new StringBuffer("abc");
StringBuffer buf2 = buf1.append("xyz");

StringBuilder builder1 = new StringBuilder("abc");
StringBuilder builder2 = builder1.append("xyz");

System.out.println(str1==str2);
System.out.println(buf1==buf2);
System.out.println(builder1==builder2);
}
}

【例09_16】中分别用String类的concat()方法以及StringBuffer和StringBuilder的append()方法对字符串进行了拼接。之后用拼接产生的字符串和原字符串进行比较。【例09_16】的运行结果如图9-11所示:​

第九章《字符串》第4节:StringBuffer和StringBuilder

    图9-11【例09_16】运行结果​

从图9-11可以看出:String类在对字符串进行拼接之后会产生新的对象,而StringBuffer和StringBuilder在对字符串进行拼接之后没有产生新对象,这说明拼接操作是在原对象上直接进行的。​

Java语言中定义StringBuffer和StringBuilder这两类就是为了在修改字符串时不产生新的对象,因此这两个类中定义了许多用来修改字符串的方法。下面的表9-1就列举了这两个类中用于修改字符串的方法:​

表9-1 修改字符串的方法​

方法名

作用

append​()

字符串末尾添加数据

delete​()

去除字符串中的一部分

deleteCharAt​()

删除指定位置的字符

insert​()

字符串定位置添加数据

replace​()

替换指定位置的字符串

reverse()

反转字符串

setCharAt​()

替换指定位置的字符

表9-1中的append()方法和insert()方法其实有很多重载版本,这使得程序员可以把任意类型的数据添加到字符串中,当然,这些被添加到字符串中的数据最终也会被转换成字符串。下面的【例09_17】演示了使用这些方法对字符串进行修改的效果。​

【例09_17 修改字符串】

Exam09_17.java

public class Exam09_17 {
public static void main(String[] args) {
//字符串末尾添加boolean型数据
System.out.println(new StringBuffer("abcde").append(true));//①
//字符串末尾添加double型数据
System.out.println(new StringBuffer("abcde").append(1.25));
//删除下标从1开始到下标为3之前的部分字符
System.out.println(new StringBuffer("abcde").delete(1, 3));
//在下标为2的位置插入单个字符
System.out.println(new StringBuffer("abcde").insert(2,'x'));
//在下标为3的位置插入字符串
System.out.println(new StringBuffer("abcde").insert(3,"##"));
//替换下标从1开始到下标为3之前的部分字符
System.out.println(new StringBuffer("abcde").replace(1, 3,"**"));
//反转字符串
System.out.println(new StringBuffer("abcde").reverse());
StringBuffer buf = new StringBuffer("abcde");
//把下标为2的位置上的字符替换为@
buf.setCharAt(2, '@');
System.out.println(buf);
}
}

修改字符串的方法一般都有返回值,这个返回值就是StringBuffer对象自身被修改过的结果。但setCharAt()方法却没有返回值,所以不能直接打印方法的执行结果。【例09_17】中列举的append()方法和insert()方法有很多重载版本,这使得程序员可以把任意类型的数据添加到字符串中。【例09_17】的运行结果如图9-12所示:​

第九章《字符串》第4节:StringBuffer和StringBuilder

图9-12 【例09_17】运行结果​

各位读者可以根据表9-1以及程序的运行结果仔细体会修改字符串方法的功能和作用。​

StringBuffer和StringBuilder都是用来完成字符串修改的类,那么它们之间有什么区别呢?如果查看源代码,你会发现StringBuffer和StringBuilder这两个类中所定义的方法是完全相同的。而它们之间的区别仅仅在于StringBuffer可以保证线程同步,而StringBuilder则不能。“线程同步”是一个目前还没讲到的概念,这里举例解释一下:在生活当中有很多东西是多人共用的,比如说一个家庭里有一台电脑,任何家庭成员都可以使用它。所谓保证同步就好比是:一个人在使用电脑期间,另一个人不能同时使用,直到使用电脑的人彻底用完了之后其他人才能使用。而StringBuilder能够保证线程同步的意思是:一个线程在操作某个StringBuffer对象期间其他线程不能对这个对象有任何操作。​

线程同步虽然能够保证数据安全,但使得对象的利用率降低,并且为了实现同步还会增加系统开销。很多情况下实际上是不需要保证线程同步的,比如单机应用模式下的字符串处理往往不需要保证同步,这种情况下就可以使用StringBuilder来处理字符串。因此:选择使用StringBuffer和StringBuilder的关键就在于是否需要保证线程同步。​

前文曾经讲过:String类对象可以通过加号实现拼接操作,实际上,用加号拼接String类对象的语句在编译之后会形成两种结果:如果拼接结果编译阶段就能确定值,那么就直接把拼接表达式替换为拼接结果,例如下面的语句:​

String str = "abc"+"xyz";

这条语句会被编译为:​

String str = "abcxyz";

如果拼接结果不能在编译阶段确定值,那么拼接操作就会被编译为用StringBuilder类的append()方法连接字符串,例如:​

String s1 = "abc";
String s2 = "xyz";
String str = s1+s2;

以上代码中,str有s1和s2通过加号拼接而成,由于s1和s2之前都没有添加final关键字,所以拼接结果无法在编译阶段确定,这种情况下以上代码会被编译为:​

String s1 = "abc";
String s2 = "xyz";
String str = (new StringBuilder(String.valueOf(s1))).append(s2).toString();

通过编译结果看出:用加号拼接String类对象的操作实际上是由StringBuilder类的append()方法实现的,并且在执行完append()方法之后会调用toString()方法把StringBuilder类对象在转换为String类对象。正因为append()方法有各种重载版本,所以String类对象用加号能与各种类型的数据进行拼接。

本文字版教程还配有更详细的视频讲解,小伙伴们可以点击这里观看。