String、StringBuffer、StringBuilder的区别

此篇博客的所有源码都基于JDK1.8

一、字符串常量——String类

String类代表字符串。其类名被final修饰,所以不能被继承,方法不能被重写。使该类具有不变性。

String类的部分源码:

public final class String implements Serializable, Comparable<String>, CharSequence {
    private final char[] value;
	public String() {this.value = "".value; }
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
 }

  可以看到,字符串的实质是一个字符数组。他被private final修饰,之所以称字符串具有不变性,就是因为他的访问权限是private的,外部不能访问,又被final修饰,所以可看做具有不变性。但真正具有不变性的不是字符数组,而是指向该数组的引用value。

证明:被fianl修饰的变量真正具不可变性的是变量的引用。而不是变量本身。

public class RealSituation {

    final int[] array1 = {2,5,3};
    int[] array2 = {23,21,20};

    public void destroy() {
        // 编译警告,被final修饰的引用具有不变性。
        //array1 = array2;
        // 引用指向的对象具有可变性
        array1[0] = 22;
    }

    public static void main(String[] args) {
        RealSituation real = new RealSituation();
        System.out.println("数组未被改变之前:");
        for (int i = 0; i < real.array1.length; i++) {
            System.out.print(real.array1[i] + " ");
        }

        real.destroy();

        System.out.println("\n数组被破坏:");
        for (int i = 0; i < real.array1.length; i++) {
            System.out.print(real.array1[i] + " ");

        }
    }
}
数组未被改变之前:
2 5 3 
数组被破坏:
22 5 3 

  可以看到,数组被改变了,但其引用被final修饰不能被重新赋值,而String类并没有提供改变数组元素的API,所以String类型的字符串具有不可变性。

String类常用的API:

String replace(CharSequence target, CharSequence replacement)  ;字符串部分替换
String substring(int beginIndex, int endIndex) ;     字符串截取
String trim()  ;      去掉字符串中的前后空格

注意:这些方法返回的都是新的String对象,并不是对原String对象修改。使用时要小心。

常出的面试题:

public class StringTest {

    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = new String("hello");
        String str3 = "hello";

        System.out.println(str1 == str2);   // == 比较内存地址
        System.out.println(str1 == str3);
        System.out.println(str1.equals(str2));
    }
}

运行结果:

false
true
true

  String str1 = “hello”; 是由编译器创建的String对象,该对象存储在常量池中,当执行String str3 = “hello”;时,JVM会在常量池中寻找是否有 "hello"字符串,如果常量池中存在此字符串,则直接将字符串 "hello"的地址赋给str3。所以str1和str3的内存地址相等。由于str2是通过new创建的字符串,通过关键字new创建的对象存储在堆中,所以str2和str1、str2的地址不同。
  String类重写了Object类的equals方法。比较的是String类中封装的字符数组的内容。所以str1.equals(str2)的结果为true。

二:StringBuffer、StringBuilder讲解

  这2个类都继承自AbstractStringBuilder。实现基本相同。区别是StringBuffer是线程安全的,部分方法被关键字synchronized修饰,执行速度慢与StringBuilder,但线程安全。

StringBuffer和StringBuilder的扩容机制。(这里以StringBuffer为例)

  StringBuffer的字符数组初始容量为16,在以String类和CharSequence为参数实例化的情况下,计算String和CharSequence的长度,在此长度上加上16,即为StringBuffer的初始容量。

    public StringBuffer() {
        super(16);
    }

    public StringBuffer(int var1) {
        super(var1);
    }

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

    public StringBuffer(CharSequence var1) {
        this(var1.length() + 16);
        this.append(var1);
    }

append方法的实现稍微有些复杂。

	char[] value;  //  存储元素的字符数组
	int count;      //  字符数组中元素的个数 - 1
	
    public synchronized StringBuffer append(String var1) {
        this.toStringCache = null;
        super.append(var1);
        return this;
    }

	//  调用父类AbstractStringBuilder的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;
    }

	// 参数字符串为null的情况。可以看到,即使参数为空,value数组还是会存储一个“null"字符串,判断是否需要扩容,这应该注意。
    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;
    }

	// 判断数组空间是否溢出,如果溢出,进行扩容。
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

	// 扩容算法   原数组长度左偏移1位 + 2, 。满足条件则扩容到此大小,如不满足则直接将数组扩容到minCapacity或最大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;
    }

三、总结

String、StringBuffer、StringBuilder的区别:

  • 可变性: String是不可变的,StringBuffer和StringBuilder是可变的。
  • 线程安全性: StringBuffer的部分方法被synchronized修饰,线程安全。String的数组对象由于被final修饰,具有不可变性,所以也是线程安全的。StringBuilder线程不安全。
    存储空间:String没有多余的空间浪费。StringBuffer和StringBuilder会造成存储空间浪费。
  • 运行速度:在进行频繁的字符串拼接情况下,StringBuilder > StringBuffer > String。String每次拼接都会生成新的String对象,所以速度最慢。StringBuffer是同步的,速度次之,StringBuilder速度最快。
  • 使用场景:
    字符串定义之后,确定不会在改变的情况下,用String。
    字符串会频繁的改变,且不考虑线程安全,用StringBuilder;考虑线程安全,用StringBuffer。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值