在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.
参考:
- https://www.cnblogs.com/skywang12345/p/string01.html