Java StringBuilder 和 StringBuffer 源码分析
简介
StringBuilder
与StringBuffer
是两个常用的操作字符串的类。大家都知道,StringBuilder
是线程不安全的,而StringBuffer
是线程安全的。前者是JDK1.5加入的,后者在JDK1.0就有了。下面分析一下它们的内部实现。
继承关系
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
可以看到,两个类的继承关系是一模一样的。Serializable
是可以序列化的标志。CharSequence
接口包含了charAt()
、length()
、subSequence()
、toString()
这几个方法,String
类也实现了这个接口。这里的重点是抽象类AbstractStringBuilder
,这个类封装了StringBuilder
和StringBuffer
大部分操作的实现。
AbstractStringBuilder
变量及构造方法
char[] value;
int count;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
AbstractStringBuilder
内部用一个char[]
数组保存字符串,可以在构造的时候指定初始容量方法。
扩容
public void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > 0)
ensureCapacityInternal(minimumCapacity);
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
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);
}
扩容的方法最终是由expandCapacity()
实现的,在这个方法中首先把容量扩大为原来的容量加2,如果此时仍小于指定的容量,那么就把新的容量设为minimumCapacity
。然后判断是否溢出,如果溢出了,把容量设为Integer.MAX_VALUE
。最后把value
值进行拷贝,这显然是耗时操作。
append()方法
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
append()
是最常用的方法,它有很多形式的重载。上面是其中一种,用于追加字符串。如果str
是null
,则会调用appendNull()
方法。这个方法其实是追加了'n'
、'u'
、'l'
、'l'
这几个字符。如果不是null
,则首先扩容,然后调用String
的getChars()
方法将str
追加到value
末尾。最后返回对象本身,所以append()
可以连续调用。
StringBuilder
AbstractStringBuilder
已经实现了大部分需要的方法,StringBuilder
和StringBuffer
只需要调用即可。下面来看看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);
}
可以看出,StringBuilder
默认的容量大小为16。当然也可以指定初始容量,或者以一个已有的字符序列给StringBuilder
对象赋初始值。
append()方法
public StringBuilder append(String str) {
super.append(str);
return this;
}
public StringBuilder append(CharSequence s) {
super.append(s);
return this;
}
append()
的重载方法很多,这里随便列举了两个。显然,这里是直接调用的父类AbstractStringBuilder
中的方法。
toString()
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
toString()
方法返回了一个新的String
对象,与原来的对象不共享内存。其实AbstractStringBuilder
中的subString()
方法也是如此。
SringBuffer
StiringBuffer
跟StringBuilder
类似,只不过为了实现同步,很多方法使用lSynchronized
修饰,如下面的方法:
public synchronized int length() {
return count;
}
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
public synchronized void setLength(int newLength) {
toStringCache = null;
super.setLength(newLength);
}
可以看到,方法前面确实加了Synchronized
。
另外,在上面的append()
以及setLength()
方法里面还有个变量toStringCache
。这个变量是用于最近一次toString()
方法的缓存,任何时候只要StringBuffer
被修改了这个变量会被赋值为null
。StringBuffer
的toString
如下:
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
在这个方法中,如果toStringCache
为null
则先缓存。最终返回的String
对象有点不同,这个构造方法还有个参数true
。找到String
的源码看一下:
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
原来这个构造方法构造出来的String
对象并没有实际复制字符串,只是把value
指向了构造参数,这是为了节省复制元素的时间。不过这个构造器是具有包访问权限,一般情况下是不能调用的。
总结
StringBuilder
和StringBuffer
都是可变字符串,前者线程不安全,后者线程安全。StringBuilder
和StringBuffer
的大部分方法均调用父类AbstractStringBuilder
的实现。其扩容机制首先是把容量变为原来容量的2倍加2。最大容量是Integer.MAX_VALUE
,也就是0x7fffffff
。StringBuilder
和StringBuffer
的默认容量都是16,最好预先估计好字符串的大小避免扩容带来的时间消耗。
Java StringBuilder 和 StringBuffer 源码分析的更多相关文章
- java线程池ThreadPoolExector源码分析
java线程池ThreadPoolExector源码分析 今天研究了下ThreadPoolExector源码,大致上总结了以下几点跟大家分享下: 一.ThreadPoolExector几个主要变量 先 ...
- 死磕 java集合之DelayQueue源码分析
问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...
- 死磕 java集合之PriorityBlockingQueue源码分析
问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...
- 死磕 java集合之PriorityQueue源码分析
问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...
- 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计
问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...
- 死磕 java集合之LinkedHashSet源码分析
问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...
- 死磕 java集合之ConcurrentHashMap源码分析(三)
本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...
- 死磕 java集合之ArrayDeque源码分析
问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...
- 死磕 java集合之LinkedList源码分析
问题 (1)LinkedList只是一个List吗? (2)LinkedList还有其它什么特性吗? (3)LinkedList为啥经常拿出来跟ArrayList比较? (4)我为什么把LinkedL ...
随机推荐
- js基本语法与变量
1.//声明变量,js是弱类型语言,变量类型声明示没有意义的.变量类型是随时可以变化的.var a = 1; a = "haha"; a = false;2.注释与 ...
- linux shell 中的 2>&1 用法说明
linux中有三种标准输入输出,分别是 STDIN,STDOUT,STDERR,对应的数字是 0,1,2. STDIN 是标准输入,默认从键盘读取信息: STDOUT 是标准输出,默认将输出结果输出至 ...
- jQuery中的选择器及筛选器
1.jQuery的介绍 1.jQuery是继prototype之后又一个优秀的Javascript框架.其宗旨是--WRITE LESS,DO MORE! 2.它是轻量级的js库,这是其它的js库所不 ...
- Codevs 3990 [中国剩余定理]
模板题 注意如何得到[a,b]区间范围内的解 #include <iostream> #include <cstdio> #include <cstring> #i ...
- BZOJ 2084: [Poi2010]Antisymmetry [Manacher]
2084: [Poi2010]Antisymmetry Time Limit: 10 Sec Memory Limit: 259 MBSubmit: 609 Solved: 387[Submit] ...
- VS2012编译log4cpp1.1.1版本
1.起因 看到官方网站上的log4cpp的代码已经更新到了1.1.1,而我目前使用的1.0.3版本,所以想使用下最新版本.在使用过程中发现相对于老版本,新版本的变化还是比较大的,特写下此文记录下. 2 ...
- 利用Lua读写本地文件
缘由 今天在使用Lua编写脚本时,需要用到读写文件的操作,很久没有使用Lua了,特写下此文来备忘一下. 简介 Lua对文件的操作与C对文件的操作基本一致,不管是参数还是方法.Lua中可以直接通过全局方 ...
- C#写鞍点问题
题目: 编写程序,找一找一个二维数组中的鞍点(即该位置上的元素值在行中最大,在列上最小.有可能数组没有鞍点). 要求 * 二维数组的大小.数组元素的值在运行时输入: * 程序有友好的提示信息 usin ...
- 菜鸟的it之路-起航
之前在知乎上看见怎么学习数据结构下一位答主的回答,他引用了N.Wirth(沃斯)的话:程序=数据结构+算法.(哈,菜鸟无法验证这句话的正确性有多大)但毫无疑问的是,数据结构应当是一名菜鸟程序狗要重点学 ...
- 2n皇后问题
此题为蓝桥杯基础练习题. 问题描述 给定一个n*n的棋盘,棋盘中有一些位置不能放皇后.现在要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都不在同一行.同一列或同一条对角线上,任意的两个白皇后 ...