Question 20171116 StringBuffer和StringBuilder的扩容机制
StringBuffer和StringBuilder都是继承自AbstractStringBuilder,它们两个的区别在于buffer是线程安全的,builder是线程不安全的,前者安全效率低,后者高效不安全:它们的扩容机制也是这样的区别,所以我们只需要分析一个的扩容就可以了,分析buffer,另一个只用把synchronized关键字去掉就是一样的.
6.1.2 StringBuffer的初始容量和扩容机制
6.1.2.1 StringBuffer的初始容量
既然是容器,那么是一定会有个初始容量的,目的在于避免在内存中过度占用内存.容器的初始容量有默认和使用构造函数申明两种.
查看API我们知道StringBuffer有以下几种构造方法:
StringBuffer() |
StringBuffer(CharSequence seq) |
StringBuffer(int capacity) |
StringBuffer(String str) |
知道了构造方法,我们来了解一下StringBuffer的容量的底层原理;
6.1.2.1.1 不声明长度,默认分配16
查看StringBuffer的空参构造,发现其容量受父类AbstractStringBuilder控制
public StringBuffer() {
super(16);
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
查看上面代码发现默认容量实际上就是创建了一个长度16的字符数组;
6.1.2.1.2 声明长度
查看StringBuffer的有参构造
public StringBuffer(int capacity) {
super(capacity);
}
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
我们发现有参构造有三个,直接给予长度的很好理解,底层是根据这个长度来创建了一个字符数组,而使用字符串的创建呢,底层的数组长度是字符串长度+16,如果是用字符序列那么和字符串是一样的,然后执行append操作.
6.1.2.2 StringBuffer的原理
StringBuffer继承了抽象类AbstractStringBuilder,在AbstractStringBuilder类中,有两个字段分别是char[]类型的value和int类型的count,也就是说,StringBuffer本质上是一个字符数组:
char[] value;
int count;
value用来存储字符,而count表示数组中已有内容的大小,也就是长度。StringBuffer的主要操作有append、insert等,这些操作都是在value上进行的,而不是像String一样每次操作都要new一个String,因此,StringBuffer在效率上要高于String。有了append、insert等操作,value的大小就会改变,那么StringBuffer是如何操作容量的改变的呢? 首先StringBuffer有个继承自AbstractStringBuilder类的ensureCapacity的方法:
public synchronized void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > value.length) {
expandCapacity(minimumCapacity);
}
}
StringBuffer对其进行了重写,直接调用父类的expandCapacity方法。这个方法用来保证value的长度大于给定的参数minimumCapacity,在父类的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);// 复制指定的数组,截取或用 null 字符填充(如有必要),以使副本具有指定的长度。
}
也就是说,value新的大小是max(value.length*2+2,minimumCapacity),上面代码中的第二个判断是为了防止newCapacity溢出。
同时,StringBuffer还有一个直接设置count大小的函数setLength:
public synchronized void setLength(int newLength) {
super.setLength(newLength);
}
函数直接调用父类的函数,父类函数如下:
public void setLength(int newLength) {
if (newLength < 0)
throw new StringIndexOutOfBoundsException(newLength);
ensureCapacityInternal(newLength); if (count < newLength) {
for (; count < newLength; count++)
value[count] = '\0';
} else {
count = newLength;
}
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
从代码中可以看出,如果newLength大于count,那么就会在后面添加'\0'补充;如果小于count,就直接使count=newLength。
StringBuffer中的每一个append和insert函数都会调用父类的函数:
public synchronized StringBuffer append(Object obj) {
super.append(String.valueOf(obj));
return this;
}
public synchronized StringBuffer insert(int index, char[] str, int offset,
int len)
{
super.insert(index, str, offset, len);
return this;
}
而在父类中,这些函数都会首先保证value的大小够存储要添加的内容:
public AbstractStringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
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;
}
public AbstractStringBuilder insert(int index, char[] str, int offset,
int len)
{//将 str 数组参数的子数组的字符串表示形式插入此序列中。其中子数组从指定的 offset 开始,包含 len 个 char。子数组的字符将被插入 index 所指示的位置。此序列的长度将增加 len 个 char。
if ((index < 0) || (index > length()))
throw new StringIndexOutOfBoundsException(index);
if ((offset < 0) || (len < 0) || (offset > str.length - len))
throw new StringIndexOutOfBoundsException(
"offset " + offset + ", len " + len + ", str.length "
+ str.length);
ensureCapacityInternal(count + len);
System.arraycopy(value, index, value, index + len, count - index);
System.arraycopy(str, offset, value, index, len);
count += len;
return this;
}
父类中通过ensureCapacityInternal的函数保证大小,函数如下:
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
如果空间不够,最终也是调用expandCapacity方法。这样就保证了随着操作value的空间够用。 还有就是,StringBuffer可以通过toString方法转换为String
public synchronized String toString() {
return new String(value, 0, count);
}
总结:StringBuffer的扩容实际上就是新建了一个数组,将原来旧数组的内容复制到新数组,扩容机制根据当前数组长度的2倍+2和新增加字符串长度+原有数组长度进行比较,如果前者小于后者,那么扩容后的长度就是后者,如果前者大于后者那么扩容后的数组长度就是前者,每次append或者insert会再次进行比较.
欢迎各位大神提问题及补充不足和警醒错误!
Question 20171116 StringBuffer和StringBuilder的扩容机制的更多相关文章
- 浅析String、StringBuffer、StringBuilder的区别以及性能区别
前奏: 比较三者之间的区别在与区别他们做相同的事情的时候的区别,那就是在我们常见的拼接字符串的时候,StringBuffer.StringBuilder调用的是appende()方法,而String很 ...
- 用大白话的方式讲明白Java的StringBuilder、StringBuffer的扩容机制
StringBuffer和StringBuilder,它们的底层char数组value默认的初始化容量是16,扩容只需要修改底层的char数组,两者的扩容最终都会调用到AbstractStringBu ...
- 浅谈JAVA中HashMap、ArrayList、StringBuilder等的扩容机制
JAVA中的部分需要扩容的内容总结如下:第一部分: HashMap<String, String> hmap=new HashMap<>(); HashSet<Strin ...
- 分析可变形字符串序列StringBuilder 以及 StringBuffer之默认大小与扩容
默认值初始化: 1. 首先明确 StringBuffer类与 StringBuilder类均继承了抽象类 AbstractStringBuilder类 无参构造方法 2. 源码中StringBuff ...
- 提高你的Java代码质量吧:正确使用String、StringBuffer、StringBuilder
一.建议 CharSequence接口有三个实现类与字符串相关:String.StringBuffer.StringBuilder,虽然它们都与字符串相关,但是其处理机制不同. 根据不同的场景,建议 ...
- StringBuffer与StringBuilder的区别,及实现原理
区别 1.StringBuffer 与 StringBuilder 中的方法和功能完全是等价的, 2.只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是 ...
- 【37】String,StringBuffer,StringBuilder区别和概念
基本的概念: 查看 API 会发现,String.StringBuffer.StringBuilder 都实现了 CharSequence 接口,内部都是用一个char数组实现,虽然它们都与字符串相关 ...
- String、StringBuffer与StringBuilder比较
关于这三个类在字符串处理中的位置不言而喻,那么他们到底有什么优缺点,到底什么时候该用谁呢?下面我们从以下几点说明一下 1.三者在执行速度方面的比较: StringBuilder > St ...
- Java String、StringBuffer、StringBuilder有什么区别
① String是Java语言非常基础和重要的类,提供了构造和管理字符串的各种基本逻辑.它是典型的immutable类,被声明成final class,所有属性也都是final的,由于它的不可变性,类 ...
随机推荐
- js获取当前日期,格式为YYYY-MM-DD
//获取当前时间,格式YYYY-MM-DD function getNowFormatDate() { var date = new Date(); var seperator1 = "-& ...
- jquery——write less,do more
rite less, do more.这句话想必是很多语言都提倡的. 在此举三个jquery的应用体现 一.绑定多个事件类型 $("div").bind("mouseov ...
- 01.c#中的访问修饰符
public 公开的 private 私有的,只能在当前类的内部访问 protected 受保护的,只能在当前内的内部以及该类的子类可以访问. internal 可以在同一个程序(项目)集中 ...
- Web前端面试指导(七):入职后的建议
7.2 关于合同 签合同的时候,看公司要求,有些是3年,有些是5年,不要怕,签了就是了,真到想走的时候,提前说一声,随时可以走,不存在什么违约赔偿. 注意:你的合同和薪资都是属于保密的,不能让公司其他 ...
- 如何在web.config文件中配置Session变量的生命周期
实例说明:在网上购物商城中,为了维护在线购物环境,一般只有注册会员才可以购买商品.实现购物功能时,先通过Session变量记录会员的登录名,然后在购买商品页面通过判断会员是否登录确定其能否购买商品. ...
- linux注册服务教程
该说明是项目完成很久之后,整理资料时的偶然发现,当时所操作的linux为中标麒麟,需要对项目进行开机自启,对llinux还不熟悉,找不到linux中的服务自启设置.辗转多次才找到了解决方案.记录以供参 ...
- 尝试VS插件
从试用vs2013开始,ide变得越来越智能,但是vs2013总是会出一些莫名其妙的问题,导致编译不成功,不能跟vs2010共享等等.于是由再次回到vs2010. 现在vs2015的update1更新 ...
- matlab中repmat函数的用法(堆叠矩阵)
matlab中repmat函数的用法 B = repmat(A,m,n) B = repmat(A,[m n]) B = repmat(A,[m n p...]) 这是一个处理大矩阵且内容有重复时使用 ...
- Siebel计划和实施
1.计划: 自上而下计划配置项目 1)首先,确定UI和应用产品功能 2)然后,确定为实现UI功能而需要在业务层所做的更改 3)最后,确定为实现业务层更改而需要在数据层所做的更改---尽可能少做更改 如 ...
- SQL Server ->> 查看SQL Server备份历史
找到最近数据库备份的细节信息,包括备份设备名和地址,最后备份时间 ------------------------------------------------------------------- ...