String StringBuffer和StringBuilder

学习Java的过程中,我们一定听过String是不可变的,String也是Java中最基本最常使用的一个类,Java在运行时保存了一个字符串池,也使String成为一个很特殊的类。
String类的不可变性
  • 打开JDK中String类的源码,其中对于值的定义如下
private final char value[];

其中保存String值是一个char数组,而此数组是使用final定义的,所以无法修改

StringBuilder和StringBuffer

StringBuilder和StringBuffer都继承自AbstractStringBuilder类,而此类对于值的定义如下

 char[] value;

所以StringBuilder和StringBuffer可以对值进行改变。

String类操作值

String对象进行改变值的操作时,会在StringPool中新建一个字符串对象,将改变值操作的引用指向新的地址。所有操作的字符串都是一个常量,保存在StringPool中,新建String对象的时候,先去StringPool寻找是否有此字符串存在,若存在,直接将对象引用指向此地址,若不存在则添加进来。这样不同的字符串变量如果是一样的值的话,则可以实现不同对象使用同一块地址,节省空间。所以String类一定要是不可变的,若可变,则StringPool无法实现,不同的引用指向了同一块内存空间,但是被其中一个引用修改了此内存空间的值,则影响其他引用的使用。

StringBuffer和StringBuilder的容量问题

StringBuffer和StringBuilder都可以对值进行操作,而值为char的数组,自然存在容量问题。
StringBuffer的构造方法:

 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);
    }

StringBuffer的构造方法有四个重载,没有参数默认容量16,使用int作为构造函数参数则以传入的int值为准,以String为参数则默认容量为String的长度+16,CharSequence 与String方法类似。StringBuilder和StringBuffer类似。
在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;
    }
   private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {//如果容量不足
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));//扩容,复制
        }
    }

   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;
    }

从源码可以看出,在append的时候先去判断容量是否足够,如果不够的话,进行扩容,扩容策略为当前长度的2倍加2,如果还不够,则直接扩到需要的长度然后进行Array类的CopyOf操作。

三者的线程安全性

String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

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

三者操作建议

操作少量的数据 = String
单线程操作字符串缓冲区下操作大量数据 = StringBuilder
多线程操作字符串缓冲区下操作大量数据 = StringBuffer

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。