string stringbuilder stringbuffer的学习记录

89 阅读5分钟

String

public final class String

类定义使用了final说明无法被继承。

//1.8版本
/** The value is used for character storage. */
private final char value[];
//1.9版本,将char数组改为byte数组,并且添加coder作为编码标记
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final byte[] value;

    /** The identifier of the encoding used to encode the bytes in {@code value}. */
    private final byte coder;
}

以下以1.8为准进行讲述:
用private和final修饰char数组,用于存储String的内容。 value是数组引用类型的,所以指向的是堆中的内存。final说明这个数组的指向地址是不会改变。 private说明私有,只能自己使用。 我们都知道string是不可变,那上面的内容该如何解释不可变呢?:

  1. 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
  2. String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。

不可变的好处

  1. 可以存储hash值,String的hash值经常被使用,比如hashmap中的key。String不可变使得对应的hash值也不变,这样只要计算一次就可以了。
  2. String pool的需要。只有string是不可变的才能是string pool的实现成为可能。
  3. 安全性。string经常被用来当做参数,string的不变性可以保证参数的内容不会发生改变,提升安全性
  4. 线程安全:String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

String Pool

在工作中,String类是我们使用频率非常高的一种对象类型。JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维护了一块特殊的内存空间,这个空间就是string pool。它由string私有地维护。

字符串的创建主要有两种方法,一种是直接字面量赋值,一种是new()方法创建string对象。

使用字面量赋值的情况下,jvm会查看string pool中是否有该字面量,如果没有,就在string pool中创建该字符串然后再返回地址,如果有则直接返回地址,所以使用直接赋值创建的string对象会指向同一地址。

使用new()的情况,jvm还是会查看stringpool中是否有该字面量,如果没有,就在stringpool中创建该字面量,然后在堆中再创建一个该字面量的string对象,将堆中的对象地址返回。如果有,就直接在堆中创建返回堆中的地址。

字符串池的优缺点:字符串池的优点就是避免了相同内容的字符串的创建,节省了内存,省去了创建相同字符串的时间,同时提升了性能;另一方面,字符串池的缺点就是牺牲了JVM在常量池中遍历对象所需要的时间,不过其时间成本相比而言比较低。

intern方法使用: 一个初始为空的字符串池,它由类String独自维护。当调用 intern方法时,如果池已经包含一个等于此String对象的字符串(用equals(oject)方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并返回此String对象的引用。 对于任意两个字符串s和t,当且仅当s.equals(t)为true时,s.instan() == t.instan才为true。所有字面值字符串和字符串赋值常量表达式都使用 intern方法进行操作。

GC回收: 字符串池中维护了共享的字符串对象,这些字符串不会被垃圾收集器回收。

stringbuilder 和 stringbuffer

类定义

public final class StringBuffer
   extends AbstractStringBuilder
   implements java.io.Serializable, CharSequence
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

从上可以看到两者都继承了相同的类AbstractStringBuilder和相同的接口,所以两者的主要区别就在于实现上。

AbstractStringBuilder类中有数组的定义。

char[] value;

用stringbulider来说明AbstractStringBuilder

stringbulider的构造函数大同小异,传入的参数有空值,string、char的相关类和int数据,传入int数据是为了初始化父类value数组的大小。别的都会在传入参数的长度基础上加16位预置空间(无参就是0)。之后如果有参就调用append方法将参数写入。如:

public StringBuilder(String str) {
    super(str.length() + 16);
    append(str);
}
public StringBuilder append(StringBuffer sb) {
    super.append(sb);
    return this;
}

append方法只有参数不同,其实现格式都一样。调用父类对应方法,返回。 那接下来看看父类的一个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;
}

其中ensureCapacityInternal实现了value数组的扩容。

private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}

str.getChars(0, len, value, count);这一步实现了内容的复制。

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
   if (srcBegin < 0) {
       throw new StringIndexOutOfBoundsException(srcBegin);
   }
   if (srcEnd > value.length) {
       throw new StringIndexOutOfBoundsException(srcEnd);
   }
   if (srcBegin > srcEnd) {
       throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
   }
   System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

所以AbstractStringBuilder类提供了可以改变的字符数组和一些可以编辑数组的方法,从而实现了这两个类的内容可变。

stringbuffer的实现也是一样。那这两者有什么不同? 从stringbuilder中可以找到下面的这一句话:

This class provides an API compatible
* with {@code StringBuffer}, but with no guarantee of synchronization.

也就是stringbuilder没有使用synchronization。而stringbuffer中使用了synchronize对方法加锁。 同样的append:

public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

三者性能差别

每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

对于三者使用的总结:

  1. 操作少量的数据: 适用 String
  2. 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
  3. 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer