前言

  相信很多程序员在使用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类是耳熟能详的类了,没什么可以介绍了的,只是在这里做一下简单的对比,String类适合很少量的字符串操作,做大量操作时相对其他StringBuilder和StringBuffer消耗时间和内存,而后面两者则都是适合做大量的拼接,截取,替换一类的操作,并且StringBuffer是线程安全的,StringBuilder是不安全的,值得一提的是这两者都继承于AbstractStringBuilder,两者本身并没有做太多的实际性工作,几乎所有的逻辑操作都在父类AbstractStringBuilder中,除了toString,toString是AbstractStringBuilder类的唯一的抽象方法。

  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;

  }