上一节我们讲解了String,这一节我们来讲解StringBuilder。同样让我们带着疑问来学习:
1.什么是StringBuilder?
2.为什么要有StringBuilder?
一、什么是StringBuilder?
StringBuilder可以看成是一个容器,创建之后里面的内容是可变的。
二、为什么要有StringBuilder?
回答这个问题之前,让我们先看一个例子:
public class demo99 {
public static void main(String[] args) {
String s1="a";
String s2=s1+"b";
String s3=s2+"c";
System.out.println(s3);
}
}
假若我要做以上代码所表达的操作,就是对字符串进行拼接,从上一节我们知道,字符串本身是不可变的,所以每一次拼接都要创建一个新的对象并返回,这里只是拼接三个,如果我要拼接上万个呢?这就需要拼接一次创建一次对象,这样就很非常影响我们的内存和程序的效率。这并不是我们想要的,我们想要的肯定是一个快捷的方式,于是就引入了StringBuilder:
作用:提高字符串的操作效率。
那么它是怎么提高字符串的操作效率的呢?直接看内存原理图和代码比较好理解为什么提高了拼接效率:
public class demo99 {
public static void main(String[] args) {
StringBuilder sb=new StringBuilder("");
sb.append("a");
sb.append("b");
sb.append("c");
System.out.println(sb);
}
}
我们这里的操作也是abc进行拼接,我们还是用javap -v demo99.class查看字节码文件
Constant pool:
#1 = Methodref #10.#19 // java/lang/Object."<init>":()V
#2 = Class #20 // java/lang/StringBuilder
#3 = String #21 // hello
#4 = Methodref #2.#22 // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
#5 = String #23 // a
#6 = Methodref #2.#24 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#7 = String #25 // b
#8 = String #26 // c
#9 = Class #27 // stringDemo/demo99
#10 = Class #28 // java/lang/Object
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 main
#16 = Utf8 ([Ljava/lang/String;)V
#17 = Utf8 SourceFile
#18 = Utf8 demo99.java
#19 = NameAndType #11:#12 // "<init>":()V
#20 = Utf8 java/lang/StringBuilder
#21 = Utf8 hello
#22 = NameAndType #11:#29 // "<init>":(Ljava/lang/String;)V
#23 = Utf8 a
#24 = NameAndType #30:#31 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#25 = Utf8 b
#26 = Utf8 c
#27 = Utf8 stringDemo/demo99
#28 = Utf8 java/lang/Object
#29 = Utf8 (Ljava/lang/String;)V
#30 = Utf8 append
#31 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
{
public stringDemo.demo99();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: new #2 // class java/lang/StringBuilder
3: dup
4: ldc #3 // String hello
6: invokespecial #4 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
9: astore_1
10: aload_1
11: ldc #5 // String a
13: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
16: pop
17: aload_1
18: ldc #7 // String b
20: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: pop
24: aload_1
25: ldc #8 // String c
27: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: pop
31: return
通过字节码文件我们可以看到常量池中仅仅是创建一个三个对象a,b,c。然后在堆中创建了一个StringBuilder对象。让我们来看下这两种拼接方式内存的原理图:
1.+连接符的拼接方式:
我们从图中可以看到,每次拼接都需要创建一个StringBuilder对象,然后StringBuilder在调用toString方法返回字符串,这种方法每次拼接都要再堆中创建两个对象。
有可能有人会疑问,为什么toString的方法还创建对象了呢?我们来一看以下源码:
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);//创建了一个新的字符串对象
}
2.StringBuilder的方法内存原理:
可以看到,所有拼接的内容都会往StringBuilder中放,不会创建很多无用的空间,节约内存,提高了效率。
学到这里可能会有人问,StringBuilder这样一直往里放内容,会不会爆呢?让我们来看一下原码分析:
public StringBuilder() {
super(16);
}
可以看到再进行初始化的时候:
默认的容量大小是16
然后,如果要进行再添加的话分两种情况:
第一种:容量足够的话,直接进行添加就行
第二种:容量不够的情况下,扩充也会分为两种:
1.添加的内容大于16会扩容,扩容后的容量是(原来的容量*2+2)
2.如果扩容之后还不够,以实际长度为准
下面是这第两种情况的代码
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;//扩充为原来的两倍+2
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;//扩充后不够以实际为准
}
}
该内容学习来自#黑马程序员java,并对其进行了扩充整理。