十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
前言
让客户满意是我们工作的目标,不断超越客户的期望值来自于我们对这个行业的热爱。我们立志把好的技术通过有效、简单的方式提供给客户,将通过不懈努力成为客户在信息化领域值得信任、有价值的长期合作伙伴,公司提供的服务项目有:主机域名、网络空间、营销软件、网站建设、玉泉街道网站维护、网站推广。
相信很多程序员在使用String、StringBuilder和StringBuffer的时候都知道怎么使用,却并不会去看其原理,
在学习这三个类之前先认识一下CharSequence接口和Appendable接口:
CharSequence接口,出自于JDK1.4,有如下几个常用的方法:
int length(); 返回字符序列长度
char charAt(int index); 返回字符序列在指定索引处的字符
CharSequence subSequence(int start, int end); 截取字符序列从索引start到end之间的值。包括start不包括end。例如:长度为5的字符序列“12345”,截图0到3的值为“123”,即真正的返回值为索引start到end-1之间的值。
Appendable接口,出自JDK1.5,有如下几个常用方法:
Appendable append(CharSequence csq) throws IOException; 拼接字符序列
Appendable append(CharSequence csq, int start, int end) throws IOException; 拼接字符序列指定区间内的字符序列,包括start不包括end。即真正拼接的值为索引start到end-1之间的值。
Appendable append(char c) throws IOException; 拼接字符
概括
String类是字符串常量,是不可更改的常量。而StringBuffer是字符串变量,它的对象是可以扩充和修改的。
StringBuffer类的构造函数
public StringBuffer()
创建一个空的StringBuffer类的对象。
public StringBuffer( int length )
创建一个长度为 参数length 的StringBuffer类的对象。
注意:如果参数length小于0,将触发NegativeArraySizeException异常。
public StringBuffer( String str )
用一个已存在的字符串常量来创建StringBuffer类的对象。
StringBuffer类
StringBuffer类,继承AbstractStringBuilder,实现Serializable序列化,操作上是线程安全的,线程安全的原因就是该类进行数据操作的相关方法都加了synchronized关键字,而StringBuilder则都没有加。
toStringCache变量:使用transient修饰,不参与序列化,作为toString方法缓存用
默认构造器:调用父类构造器,传递一个默认的长度值16,父类构造器创建一个长度为16的字符数组;
参数为int的构造器:参数为int的构造器意思是构造一个指定长度的字符数组
参数为String和CharSequence的构造器:会构造一个长度为(16+CharSequence字符长度)或者(16+String字符串长度)的字符数组。然后再拼接一下参数中的字符或者字符串,如果参数为null会抛出空指针异常(NullPointerException)。
length方法:返回实际数据长度
capacity方法:返回父类构造器的字符数组长度
public final class StringBuffer extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{
private transient char[] toStringCache;
/** use serialVersionUID from JDK 1.0.2 for interoperability */
static final long serialVersionUID = 3388685877147921107L;
public StringBuffer() {
super(16);
}
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);
}
@Override
public synchronized int length() {
return count;
}
@Override
public synchronized int capacity() {
return value.length;
}
接下来看一看一些相关操作的方法:
可以看到如拼接,插入,删除,替换之类的操作都是调用父类的方法,并且返回的是调用该方法的类对象,这就表明StringBuffer类在进行数据改变的操作后返回类对象本身,原因就是父类中储存数据的字符数组value没有final所修饰,所以可以做到修改数据而不改变类对象。
而String类中储存数据的字符数组变量value是被final修饰的,也就说明无法对String类对象的值进行直接的修改,所以对其进行数据改变操作的返回值都是new String(XXX),也就是每个返回值都是一个新的String对象,所以String类不适合大量的数据值改变的操作。
toStringCache变量:只有在调用toString方法的时候给予赋值操作,临时储存数据,然后转换为Sting对象当做返回值。而每次对数据进行改变的时候都会重置变量值,保证每次toString之前该变量都是空。
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
@Override
public synchronized StringBuffer delete(int start, int end) {
toStringCache = null;
super.delete(start, end);
return this;
}
@Override
public synchronized StringBuffer replace(int start, int end, String str) {
toStringCache = null;
super.replace(start, end, str);
return this;
}
@Override
public synchronized StringBuffer insert(int index, char[] str, int offset,
int len)
{
toStringCache = null;
super.insert(index, str, offset, len);
return this;
}
@Override
public int indexOf(String str) {
// Note, synchronization achieved via invocations of other StringBuffer methods
return super.indexOf(str);
}
@Override
public synchronized int indexOf(String str, int fromIndex) {
return super.indexOf(str, fromIndex);
}
@Override
public int lastIndexOf(String str) {
// Note, synchronization achieved via invocations of other StringBuffer methods
return lastIndexOf(str, count);
}
@Override
public synchronized int lastIndexOf(String str, int fromIndex) {
return super.lastIndexOf(str, fromIndex);
}
@Override
public synchronized StringBuffer reverse() {
toStringCache = null;
super.reverse();
return this;
}
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
StringBuilder类
StringBuilder类和StringBuffer差不多,少了一个toStringCache变量,所以的操作方法都没有添加sybchronized关键字,所以StringBuilder是线程不安全的类。就不多说了哈(*^▽^*)。
AbstractStringBuilder
AbstractStringBuilder产于JDK1.5,实现Appendable接口和CharSequence接口
该类只能被继承,有两个子类StringBuffer和StringBuilder,会默认调用有参构造器,指定初始化的字符串数据长度
value: 实例化时创建出来的字符数组
count: 实际数据包含的字符的长度
length方法:返回实际数据包含的字符的长度
capacity方法:返回字符数组的大小
代码如下:
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
@Override
public int length() {
return count;
}
public int capacity() {
return value.length;
}
数据储存空间操作相关方法:
ensureCapacityInternal:每次进行数据改变操作之前都会调用的方法,其意在于确保数组变量value有能力承受接下来的改变,说白了就是对数据操作的之前改变字符数组大小,使其容量能足够接下来的操作使用而不出现错误。
MAX_ARRAY_SIZE:私有静态常量,值为 Integer.MAX_VALUE - 8,按照文档翻译来说,就是字符数组的最大容量,但是却没有达到Integer的最大值,原因是某些JVM虚拟机会在一个数组中保留一些标题词,如果强行尝试区分配超过这个容量的数组可能会导致抛出异常OutOfMemoryError:请求的数组大小超过VM限制。
newCapacity:重新设置字符数组的容量,并作为返回值。参数表示字符数组的最小容量,首先要知道字符串数组初始长度为16,扩容方式为原字符数组长度左移一位后再加2(原字符数组长度乘以2,再加2)。该方法就是比较一次扩容后value长度值newCapacity和参数指定的最小值minCapacity,如果一次扩容满足最小值需求,则使用newCapacity,如果不满于则直接使用minCapacity并且赋值于newCapacity,最后判断minCapacity的大小是否大于0并小于指定的MAX_ARRAY_SIZE的值,如果满足则返回minCapacity的值,如果不满足则表示要求的最小值超过了建议的最大值容量,那将把minCapacity传递给为hugeCapacity方法,并以该方法的返回值作为本方法的返回值,如下。郑州专业妇科医院 http://www.hnzzkd.com/
hugeCapacity:如果参数给定值超过Integer的类型最大值,抛出内存溢出异常,如果小于Integer的最大值,则和AbstractStringBuilder类的建议值对比,哪一个值大,则使用哪一个值作为返回值。
trimToSize:去除多余的数组储存空间,提高空间利用率。比较数组空间value的大小和实际数据count大小,如果实际数据元素小于value,则表示实际数据并未占满分配的空间,调用Arrays.copyOf方法把value中的空间铺满,返回铺满后的value值,
setLength:设置当前序列的长度为指定的参数长度newLength,如果当前序列的长度超出指定长度newLength,就把已有序列的前面长度为newLength的字符拷贝到新字符序列里,多出来的一部分舍弃。如果不超过newLegth,原有数据不变,就把缺少的几个位置的数据设置为空字符。
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
public void trimToSize() {
if (count < value.length) {
value = Arrays.copyOf(value, count);
}
}
public void setLength(int newLength) {
if (newLength < 0)
throw new StringIndexOutOfBoundsException(newLength);
ensureCapacityInternal(newLength);
if (count < newLength) {
Arrays.fill(value, count, newLength, '\0');
}
count = newLength;
}
接下来看一下实际的数据操作,这里以最常用的拼接和替换为例:
appendNull:先扩容,然后依次拼接字符n u l l,拼接的同时进行count++;
append:先判断字符串是否为null,如果是执行appendNull,如果不是空,则获取目标对象的长度,然后进行扩容,然后把目标对象拷贝到value的后面。然后更新数据的大小count。返回类对象
replace:先判断一系列索引越界问题,如果都没问题检查end是否超出count,超出了则修改end的值为count,获取目标字符串的长度,计算经过替换后的数据长度。原长度减去要被替换的长度加上要替换成的字符串长度:count-(end-start)+len。计算完成后更新字符数组大小,拷贝字符后更新count的值。返回类对象
private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}
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;
}
public AbstractStringBuilder replace(int start, int end, String str) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (start > count)
throw new StringIndexOutOfBoundsException("start > length()");
if (start > end)
throw new StringIndexOutOfBoundsException("start > end");
if (end > count)
end = count;
int len = str.length();
int newCount = count + len - (end - start);
ensureCapacityInternal(newCount);
System.arraycopy(value, end, value, start + len, count - end);
str.getChars(value, start);
count = newCount;
return this;
}
public AbstractStringBuilder delete(int start, int end) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
end = count;
if (start > end)
throw new StringIndexOutOfBoundsException();
int len = end - start;
if (len > 0) {
System.arraycopy(value, start+len, value, start, count-end);
count -= len;
}
return this;
}