String、Stringbuffer和StringBuilder的异同? String 为什么是不可变的?

String、Stringbuffer和StringBuilder的异同? String 为什么是不可变的?

1.可变性

简单的来说:String 类中使用 final 关键字修饰字符数组char[]来保存字符串,private final char value[],所以String 对象是不可变的。

补充:在 Java 9 之后,String 、StringBuilderStringBuffer 的实现改用 byte 数组存储字符串 private final byte[] value

为什么使用byte字节而舍弃了char字符:

节省内存占用,byte占一个字节(8位),char占用2个字节(16),相较char节省一半的内存空间。节省gc压力。
针对初始化的字符,对字符长度进行判断选择不同的编码方式。如果是 LATIN-1 编码,则右移0位,数组长度即为字符串长度。而如果是 UTF16 编码,则右移1位,数组长度的二分之一为字符串长度。

StringBuilderStringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串char[] value 但是没有用 final 关键字修饰,所以这两种对象都是可变的。

StringBuilderStringBuffer 的构造方法都是调用父类构造方法也就是AbstractStringBuilder 实现的.

2.线程安全性

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

3.性能

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

对于三者使用的总结:

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

内存源码解析:

String类

//底层是final修饰的字符数组 //char[0] 长度为0
String a=new String();
//char[3]={'a','b','c'}  长度为3
String b=new String("abc");

StringBufferStringbuilder两个类都继承自 AbstractStringBuilder 类,都调用了父类的构造方法,而父类使用的是字符数组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);
}

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


从源码中可以看出无参构造StringBuffer stringBuffer = new StringBuffer();StringBuilder stringBuilder = new StringBuilder();默认的初始化容量是16的字符数组

StringBuffer

public StringBuffer() {
    super(16);
}

StringBuilder

public StringBuilder() {
    super(16);
}

而有参构造分为三种,一种是指定长度,另外一种是传入字符串,还有一种是传入字符序列

StringBuffer

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

StringBuilder

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

从源码中可见,如果传入的是int数值,则创建该对应长度的char[]数组,如果是字符串和字符序列CharSequence

则创建传入的字符串或者字符序列的长度+16的字符数组

同时,因为StringBufferStringBuilder是可变的字符序列,所以就会牵扯到char[]字符数组的扩容问题

这里以StringBuffer为例:

首先从有参构造点开来看:

    public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }

点开append方法:发现StringBuffer重写了父类的append方法,并调用父类的append方法

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

点开super.append(str);方法,看到了ensureCapacityInternal(count + len);这个确保内部容量的方法,cout为最小容量

    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方法:minimumCapacity=当前剩余最小容量+当前输入的字符串长度

如果minimumCapacity大于当前字符数组的长度,则需要扩容,并把之前的内容拷贝到新字符数组中

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

点开newCapacity方法:

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

从源码可见,扩容的新字符数组的长度当前字符数组的长度左移一位,并加2,也就是当前的字符数组长度✖️2+2

如果minCapacity最小容量小于零或大于Integer.MAX_VALUE,则所需的最小容量@抛出OutOfMemoryError

点开hugeCapacity(minCapacity)方法:

    private int hugeCapacity(int minCapacity) {
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }

发现如果Integer.MAX_VALUE-当前最小容量小于0,则抛出内存不足异常OutOfMemoryError()

否则就判断当前最小容量是否大于MAX_ARRAY_SIZE,如果大于就返回最小容量,小于就返回 ( 231-1)-8

Integer.MAX_VALUE= 231-1

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值