在Java中,我们有三种存储字符串的方式:String、StringBuffer、StringBuilder。这篇文章主要是整理一下他们各自的优缺点和使用场景。该问题也是面试中常常被问到的基础知识。

1.三者区别

1.1 是否可变

String定义的字符串不可被改变。 每次对String对象进行改变的时候,其实是Java新创建了一个对象,然后将之前的指针指向的新的对象。这样做不仅浪费了很多空间,还降低了效率。

而StringBuilder和StringBuffer是可变对象。它们都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串。因此他们的类创建的字符串对象能够被多次修改,而不产生新的未使用对象。他们俩可使用的方法也是相同的。

1.2 线程安全

**StringBuilder是线程不安全的,StringBuffer是线程安全的。**因此单线程下使用StringBuilder会效率更高,多线程下使用StringBuffer会效率更高。操作小数据的话,还是String效率高。

1.3 应用场景

String使用场景:

  • 字符串内容不长发生改变的业务场景
  • 比如:常量声明、少量字符串拼接

StringBuffer应用场景:

  • 频繁进行字符串运算时(拼接、替换、删除)
  • 多线程时、因为它支持线程安全
  • 比如:XML解析、HTTP参数的解析与封装

StringBuilder应用场景:

  • 频繁进行字符串运算时(拼接、替换、删除)
  • 单线程时,因为它不支持线程安全
  • 比如:SQL语句拼接、Json数据封装

2.StringBuffer

了解StringBuffer之前,我们先回顾一下String的特点,String字符串一旦定义就不能修改也不能扩充的,而且,当使用连接符对字符串进行拼接的时候java会为字符串新开辟空间,这样就造成了空间的浪费。

为此StringBuffer作为解决方案应运而生,他是线程安全的可变字符序列。他是一个用final修饰的类,它继承于Object,实现了两个接口。该接口定义后内容可变,大小可变,使用拼接不会产生新的空间。他就类似于一个字符串的缓冲区。

2.1 容量和长度

StringBuffer有两个属性,一个是容量,一个是当前长度。容量代表的是能容纳多大的字符串,默认是16个字符大小。

2.2 构造方法

它有三个构造方法,一个是无参的(最常用),一个是传入整型参数的,用于指定容量,指定了多少,容量就变成多少。一个是字符串参数的,用于指定内容,容量是16+参数的长度。

new StringBuffer(); //容量为16
new StringBuffer(int capacity);	//容量是int
new StringBuffer(String str);	//容量是str.length()+16

2.3 常用方法

  • append:追加

可以将任意的类型的数据添加到字符串的缓冲区,返回值是StringBuffer,就是它缓冲区本身。

StringBuffer sb = new StringBuffer();	//创建一个水杯
StringBuffer sb1 = sb.append("hello");	//向水杯里面装水,然后将水杯返回。水杯还是原来的水杯。只不过加了个别名
System.out.println(sb); //输出hello
System.out.println(sb1); //输出hello
System.out.println(sb == sb1); //输出true

因此,直接sb.append()即可,无需使用引用进行接收。

StringBuffer sb = new StringBuffer();
sb.append(true).append(123).append("heihei");	//链式编程
System.out.println(sb);							//输出true123heihei
  • insert:插入

此方法可指定位置插入任意类型数据。从0开始计数。

sb.insert(5,"world");
  • deleteCharAt:删除指定位置的一个字符
public StringBuffer deleteCharAt(int index);
  • delete(int start, int end ):删除指定长度字符串
public StringBuffer delete(int start, int end);
  • replace(int start, int end, String str):替换指定位置、长度字符串
public StringBuffer replace(int start, int end, String str);
  • reverse():翻转
public StringBuffer reverse();
  • 字符串截取
public String substring(int start); //从start开始截取直到最后
pubic String substring(int start, int end); //从start截取,到end结束

注意返回的是字符串

2.4 String到StringBuffer的转换

错误转换方式:

StringBuffer sb = "hello";
Stringbuffer sb = s;

正确的方式:

StringBuffer sb = new StringBuffer(s);
StringBuffer sb1 = new Stringbuffer();
sb1.append(s)

2.5 StringBuffer到String的转换

1.通过构造方法

String str = new String(buffer)

2.通过toString()

String str = buffer.toString()

任何引用类型调用toString都可以转换成字符串。

2.6 String与StringBuffer作为形参传递的不同

对于基本数据类型来说,形参的改变不影响实参,但是对于引用数据类型来说,形参的改变直接改变了实际的参数。

但是字符串虽然是引用类型,但他是比较特殊的,因为它是常量,存储在字符串常量池中。是一种特殊的引用类型,我们可以将它是为基本数据类型,所以当字符串作为形参的时候,形参改变但实参是不改变的。

StringBuffer是正常的引用类型,形参改变则实参改变。

3.可变原理

StringBuffer(始于 JDK 1.0 )和StringBuilder(始于 JDK 1.5)都是为了提高字符串的拼接效率,直接使用String的+进行拼接的话JVM会创建多个字符串对象,造成开销浪费。他俩用法一样,只不过Buffer是线程安全,Builder是线程不安全的。

String源码中有一个成员是,使用了final修饰,意思是不能更改:

private final char value[];

而Buffer和Builder的成员同样也是一个字符数组,但是没有使用final修饰:

char value[];

所以这就是可变和不可变的基础和前提。

当要存入字符串的长度+value的长度-value的长度>0的时候,说明,此时的容量已经不足以装下新的字符串了。然后Java会将原来的字符数组复制一份(使用的是str.getchars()),然后开辟一个新的数组,赋予新的容量,该容量是str.length()+value,最后return this,返回当前对象。返回当前对象的好处就是可以写成链式编程。

String只要已添加就去开辟新对象,而Buffer和Builder只有在容量不够的时候才去开辟新对象。

4.注意事项

StringBuilder sb1 = new StringBuilder("abc");
StringBuilder sb2 = new StringBuilder("abc");

System.out.println(sb1 == sb2);//false
System.out.println(sb1.equals(sb2));//false

第一个返回的是false是因为它比较的是对象的地址值,而第二个返回的是false是因为StringBuilder没有重写equals方法,使用的是Object的比较方式,也就是比较地址值。所以,要想用值来比较,应该重写equals方法。

String str1 = "abc";
StringBuilder sb1 = new StringBuilder("abc");
System.out.println(str1.equals(sb1));//false

虽然string重写了equals方法,但是,sb1对象不是String类的实例,所以返回的也是false.