字符串不是有String
吗?还要StringBuilder
干嘛啊?
其实StringBuilder
准确的来说是字符串缓冲区。String定义的字符串是常量,创建后不可变因为String
的底层源码是:
@Stable
private final byte[] value;
有final
修饰还变个🔨啊,但是字符串缓冲区却支持可变字符串。因为其底层源码为。
private byte[] value;
没有被final
修饰。
且StringBuilder
在做字符串拼接时其效率很高,较String
无脑式加法操作来说,举个例子:
String str = "Author"+" is"+" handsome!";
> Author is handsome!
输出以上内容的步骤可以分解为:
String str = "Author"+" is"+" handsome!";
/*
*Step1: "Author"+" is"+" handsome!" 三个字符串
*
*Step2: "Author is" + "handsome!" 此时一个新的字符串产生Author is
*
*Step: "Author is handsome!" 此时拼接完成,又产生最后一个结果字符串。
*/
在这个过程中,中间的两两相加,会占用内存,如果需要拼接的元素很多,就会明显变慢。而StringBuilder
中的append
方法会使这个过程提速很多。(后面会具体解释为什么快)
StringBuilder类
StringBuilder
类又叫字符串缓冲区,可以看作长度可变的字符串。长度可变是因为其底层源码未使用final
修饰。
具体使用方法分为两种(有参/无参):
public StringBuilder()
:构造一个空StringBuilder容器(长度为16)。public StringBuilder(String str)
:构造一个StringBuilder容器并同时添加字符串。
//StringBuilder源码
public StringBuilder() {
super(16);
}
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
//AbstractStringBuilder源码 (StringBuilder的父类)
//成员变量
byte[] value;
byte coder;
int count;
//无参构造
AbstractStringBuilder() {
}
//有参构造
AbstractStringBuilder(int capacity) {
if (COMPACT_STRINGS) {
value = new byte[capacity];
coder = LATIN1;
} else {
value = StringUTF16.newBytesFor(capacity);
coder = UTF16;
}
}
阅读上述代码可知,使用无参构造StringBuilder
出的字符串,里面“什么都没有”,其默认长度为16
且StringBuilder
类的父类是一个抽象类,同时也作为Appendable
与CharSequence
接口的实现:
abstract class AbstractStringBuilder implements Appendable, CharSequence {
.... ....
}
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{
.... ....
}
StringBuilder
类不但继承了父类中部分方法,也重写了其中的一部分。
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
@Override
@HotSpotIntrinsicCandidate
public StringBuilder append(String str) {
super.append(str);
return this;
}
// .... ....
public StringBuilder append(StringBuffer sb) {
super.append(sb);
return this;
}
// .... ....
由此可知,StringBuilder
中的append()
方法支持多种数据类型参数(重载)。
append()方法
上文提到的对于字符串拼接效率高于传统的String
加法:
//下面为两种String类型的字符串拼接过程
String str1 = "Surprise ";
String str2 = "Mother ";
String str3="Fucker!";
String str4 = str1 + str2 + str3;
System.out.println(str4);
//>Surprise Mother Fucker!
//或:
String str = "Hello "+"Java";
System.out.println(str);
//>Hello Java
使用StringBuilder
内置append()
方法来执行上面的操作效率会高很多。具体原因在于,StringBuilder
中的append()
方法,每次返回值不是一个新的对象,而是其对象执行操作后的本身(this
),没有中间量的生成就不会占用额外的内存去存储中间过程中产生的值。具体体现源码为:
@Override
@HotSpotIntrinsicCandidate
public StringBuilder append(String str) {
super.append(str);
return this;
}
public StringBuilder append(StringBuffer sb) {
super.append(sb);
return this;
}
上面是该类的源码,可见每次返回值均为this,每次操作也均无中间值
产生。
我们可以进行一个简短的验证:
public static void main(String[] args){
//代参数初始化创建StringBuilder对象为字符串"biu~"
StringBuilder biu = new StringBuilder("biu~");
System.out.println(biu);
//向该类型字符串后添加"biu~"
StringBuilder biubiu = biu.append("biu~");
System.out.println(biubiu);
//再添加"biu~"
StringBuilder biubiubiu = biu.append("biu~");
System.out.println(biubiubiu);
}
运行的结果为:
为了确定其每次操作都没有产生新的值,而是只对该对象自身操作,我们可以比较他们的地址值,来确定是否发生改变。
public static void main(String[] args){
//代参数初始化创建StringBuilder对象为字符串"biu~"
StringBuilder biu = new StringBuilder("biu~");
System.out.println(biu);
//向该类型字符串后添加"biu~"
StringBuilder biubiu = biu.append("biu~");
System.out.println(biubiu);
//再添加"biu~"
StringBuilder biubiubiu = biu.append("biu~");
System.out.println(biubiubiu);
//----------------Cross Line------------------
boolean flag1 = biu == biubiu;
boolean flag2 = biubiu == biubiubiu;
System.out.println("biu_ADDR == biubiu_ADDR ? " + flag1);
System.out.println("biubiu_ADDR == biubiubiu_ADDR ? " + flag2);
}
结果如下:
其地址值相等,可见期间该地址值为发生改变,没有所谓的中间步骤。是对对象本身进行操作,最后又返回该对象本身(return this;
)
由此点特性我们还能知道,使用该方法来操作拼接字符串,不需要接收返回值,因为append()
方法本质就是修改StringBuidler
对象,最后返回该对象。
由于每次append()
都会return this
的特点,我们可以对StringBuilder
对象进行链式编程:
public static void main(String[] args){
//初始化为空,也可带参数初始化。
StringBuilder sb = new StringBuilder();
//可以写在一行,这样写是为了结构清晰
sb.append("1")
.append("2")
.append("3")
.append("4")
.append("5");
System.out.println(sb);
}
运行结果:
String 与 StringBuilder
String和StringBuilder之间可以相互转换
String to StringBuilder
使用StringBuilder的构造方法:public StringBuilder(String str)
构造一个StringBuilder容器并同时添加字符串。
StringBuilder to String
使用StringBuilder的toString方法,在源码中StringBuilder重写(Override)了toSting
方法。
public static void mian(String[] args){
//String --> StringBuilder
String str1 = "ooaa";
StringBuilder sb = new StringBuilder(str1);
//StringBuilder --> String
//匿名对象的StringBuilder
String str2 = new StringBuilder("ooaa").toString;
//命名对象的StringBuilder
StringBuilder oa = new StringBuilder("ooaa");
String str3 = oa.toString();
}
由于这个方法比较简单,这里不作过多赘述。关于StringBuilder的介绍就到这里,本文只详细介绍了StringBuilder类中常用的两种方法,StringBuilder类中还有很多骚操作:
@Override
public StringBuilder delete(int start, int end)
@Override
public StringBuilder deleteCharAt(int index)
@Override
public StringBuilder replace(int start, int end, String str)
@Override
public int indexOf(String str)
.... ....
如果你已经理解上述方法,可以再看一看上述方法在StringBuilder类的源码中的定义。(PS:以上方法都被重写过,返回值均为StringBuidler
对象本身,这就是为什么说使用StringBuilder
类中方法对字符串进行操作效率会高很多!)