StringBuilder原理(自己的理解)
构建字符串
- 为什么要使用stringbuilder:
有些时候,需要由比较短的字符串构建字符串,例如,按键或来自文件中的单词。采用字符串链接的方式达到此目的效率比较低。每次链接字符串,都构建一个新的String对象,即耗又浪费空间。使用stringbuilder类就可以避免这个问题的发生。 - 使用步骤:
如果需要用许多小段的字符串构建一个字符串,那么应该按照下列步骤进行。
首先,构建一个空的字符串构造器:
stringbuilder builder = new stringbuilder();
当每次需要添加一部分内容时,就调用append方法
builder .append(ch);
builder .append(str);
在需要构建字符串时就调用toString方法,将可以得到一个String对象,其中包含了构建器中的字符序列
String completedString = builder .toString();
- 使用StringBuilder类比使用String快很多
StringBuilder基本实现原理
一. StringBuilder类
内部组成和构造方法与String类似,StringBuilder类也封装了一个字符数组,定义如下:
char[] value;
与String不同,它不是final的,可以修改。另外,与String不同,字符数组中不一定所有位置都已经被使用,它有一个实例变量,表示数组中已经使用的字符个数,定义如下:
int count;
StringBuilder继承自AbstractStringBuilder,它的默认构造方法是:
public StringBuilder() {
super(16);
}
调用父类的构造方法,父类对应的构造方法是:
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
也就是说,new StringBuilder()这句代码,内部会创建一个长度为16的字符数组,count的默认值为0。
二. append方法
append方法的代码实现:
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
append会直接拷贝字符到内部的字符数组中,如果字符数组长度不够,会进行扩展,实际使用的长度用count体现。具体来说ensureCapacityInternal(count+len)会确保数组的长度足以容纳新添加的字符,str.getChars会拷贝新添加的字符到字符数组中,count+=len会增加实际使用的长度。
ensureCapacityInternal的代码如下:
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
如果字符数组的长度小于需要的长度,则调用expandCapacity进行扩展,expandCapacity的代码是:
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
扩展的逻辑是,分配一个足够长度的新数组,然后将原内容拷贝到这个新数组中,最后让内部的字符数组指向这个新数组,这个逻辑主要靠下面这句代码实现:
value = Arrays.copyOf(value, newCapacity);
那newCapacity是怎么算出来的?
参数minimumCapacity表示需要的最小长度,需要多少分配多少不就行了吗?不行,因为那就跟String一样了,每append一次,都会进行一次内存分配,效率低下。这里的扩展策略,是跟当前长度相关的,当前长度乘以2,再加上2,如果这个长度不够最小需要的长度,才用minimumCapacity。
比如说,默认长度为16,长度不够时,会先扩展到162+2即34,然后扩展到342+2即70,然后是70*2+2即142,这是一种指数扩展策略。为什么要加2?大概是因为在原长度为0时也可以一样工作吧。
为什么要这么扩展呢?这是一种折中策略,一方面要减少内存分配的次数,另一方面也要避免空间浪费。在不知道最终需要多长的情况下,指数扩展是一种常见的策略,广泛应用于各种内存分配相关的计算机程序中。
那如果预先就知道大概需要多长呢?可以调用StringBuilder的另外一个构造方法:
public StringBuilder(int capacity)
StringBuilder类中的API
- 在JDK5.0中引入 StringBuilder类。 这个类的前身是StringBuffer,其效率稍低,但允许采用多线程的方式执行添加或删除字符的操作。如果所有字符串在一本程中编辑(通常都是这样),则应该用StringBuilder替代它。
- 这两个类的API是相同下面的API注释包含了StringBuilder类中的重要方法。
1.StringBut1der( )
构造一个室的字符串构建器。
2.int length( )
返回构建器或缓冲器中的代码单元数量。
3.StringBuilder append(String str)
追加一个字符串并返回this。
4.StringBuilder append(char c)
追加一个代码单元并返回this。
5.StringBuilder appendCodePoint(int cp)
追加一个代码点,并将其转换为-个或两个代码单元并返回this.
6.void setCharAt(int i ,char c)
将第i个代码单元设置为c。
7.StringBuilder insert(int offset,String str)
在offset位置插人一个字符串并返回this。
8.StringBuilder insert(int offset,Char c)
在offset位置插人个代码 单元并返回this。
9.StringBuilder delete(int startIndex, int endIndex)
删除偏移 量从startIndex到-endIndex- I的代码单元并返回this.
10.String toString( )
返问一个与构建器或缓冲器内容相同的字符串。
中间StringBuilder基本实现原理部分借鉴了这个微博,此微博中有对StringBuilder的详解 https://blog.youkuaiyun.com/qq_17505335/article/details/52806096