String ,StringBuilder,StringBuffer存储形式的差异

为什么要使用StringBuilder和StringBuffer类,明明它们提供的方法功能都差不多,字符串分割,字符串查找,字符串拼接,等等,能用SringBuilder和StringBuffer类实现的功能,String类也都能实现,那为什么还要使用他们呢?

首先我们先来介绍String类

java中的string的值是通过char[]数组来存储的

 private final char[] value;

注意观察,我们可以发现 这个用于存储字符串值的字符数组 value 被final关键词修饰,final表示最终的也就是一旦赋值就不可变的意思,当我们新建一个String类型的实例时,String的构造方法就会为生成一个char[]数组,并把这个数组的地址永久的付给赋给value,直至这个String实例在内存中被销毁。例如,我们新建一个内容为”你好“的String实例

String str=new String("你好");

new String("你好")会调用String的有参构造方法生成一个内容为{'你','好'}的char[]数组并把这个数组的地址给value,当这个String实例被创建好后再把其地址赋给str。

因为value的值不能更改,所以在它的生命周期里它只能指向{'你','好'}这个char[]数组,而数组一但创建就无法再更改它的长度,因此只要str还指向这个String实例,它的字符串长度就无法更改。

但对数组有些了解的人就会产生新的疑问,数组虽然无法改变长度,但却可以改变数组中的元素,既然字符串的内容是由char[]数组存储的,那我们的字符串的内容不应该也是可变的么?

观察String类中的成员方法我们可以发现,String中返回类型为String的方法,返回的String实例都是都过new String(实参列表)得到的新字符串实例,也就是说String类提供给我的方法并不是“按我们需求对字符串操作”的方法,而是“创建符合我们需求字符串”的方法。

那么这时又有人会说,只要满足我们的需求,我们又何必去在意它底层是如何运行的呢,因为众所周知“封装”就是使Java与众不同的原因之一。

No, things are far from that simple!!!

public static void main(String[] args) {
	String str="";
	for(int i=0;i<=100;i++) {
		str=str.concat(String.valueOf(i)).concat(" ");
	}System.out.println(str);
}

现在我们想要实现1到100所有整数的字符串拼接

concat()使String类提供我们的字符串拼接方法

valueOf()使String提供给我们将其他类型转化成字符串的静态方法

public String concat(String str) {
        if (str.isEmpty()) {
            return this;
        }
        return StringConcatHelper.simpleConcat(this, str);
    }

concat()的返回 StringConcatHelper.simpleConcat(this, str);

StringConcatHelper.simpleConcat()方法

static String simpleConcat(Object first, Object second) {
        String s1 = stringOf(first);
        String s2 = stringOf(second);
        if (s1.isEmpty()) {
            // newly created string required, see JLS 15.18.1
            return new String(s2);
        }
        if (s2.isEmpty()) {
            // newly created string required, see JLS 15.18.1
            return new String(s1);
        }
        // start "mixing" in length and coder or arguments, order is not
        // important
        long indexCoder = mix(initialCoder(), s1);
        indexCoder = mix(indexCoder, s2);
        byte[] buf = newArray(indexCoder);
        // prepend each argument in reverse order, since we prepending
        // from the end of the byte array
        indexCoder = prepend(indexCoder, buf, s2);
        indexCoder = prepend(indexCoder, buf, s1);
        return newString(buf, indexCoder);
    }

StringConcatHelper.simpleConcat()方法返回 newString(buf, indexCoder)

newString()方法

 static String newString(byte[] buf, long indexCoder) {
        // Use the private, non-copying constructor (unsafe!)
        if (indexCoder == LATIN1) {
            return new String(buf, String.LATIN1);
        } else if (indexCoder == UTF16) {
            return new String(buf, String.UTF16);
        } else {
            throw new InternalError("Storage is not completely initialized, " + (int)indexCoder + " bytes left");
        }
    }

过程虽然曲折,但其实我们只用看newString()方法中的返回值

  return new String(buf, String.LATIN1);

return new String(buf, String.UTF16);

new String()!!!是不是很眼熟,String的构造方法,也就是说,concat()会新建一个String实例

这就表明我们每次拼接都会产生一个新的String实例,但我们需要的只有对后一次拼接产生的String实例,而之前产生的String实例不会直接消失,当我们循环拼接的次数特别大时,就会导致内存空间被大量浪费。

看到这里还觉得无所谓么

为了解决这种情况,我们的StringBuilder和StringBuffer类就出现了

这里以StringBuilder为例

StringBuilder继承AbstractStringBuilder, 虽然AbstractStringBuilder也是用char[]字符串存String的内容但他的数组并没用用final修饰,并且它还有一个非常重要的byte变量count用于表示有效长度

 char[] value;


 byte coder;

String中的char[]的长度和内容的字符数相等,并且因为value被final修饰导致无法指向一个新的数组。

但AbstractStringBuilder中允许char[]的长度大于内容的字符书,在实例化StringBuilder对象时,会先创建一定长度的char[]数组,并给count初始值为0,表示有效长度为0,当我们拼接字符串时,每次往value中存一个字符,count加1,表示有效长度加1,当value指向的char[]被加满是时,就会新建更大的char[]数组复制之前char[]的内容并将复制到新数组中,并将新数组的地址赋给value从而达到了不定长的效果。

因为有count记录有效长度,所以允许数组长度大于实际存入字符数,当我们进行删除,拼接,

替换,我们并不需要考虑长度不可变和有效内容到哪里结束的问题。

并且因为数组长度允许有空余,所以并不是每次拼接字符串时都新建一次数组,而是在不够存的时候才去创建一个长度为原来2倍+2的新数组,这样就会减少创建数组的次数,从而减少运行过程中产生的中间产物。

StringBuilder因为在count有效长度的帮助下,能够在对自己进行添加,删除,替换,后仍能确定有效内容到哪里结束,使之可以对自己的长度内容自由修改。因此StringBuilder提供的方法也是“按我们意愿对自身进行修改”的方法。

StringBuilder和StringBuffer都是继承AbstractStringBuilder,因此他们底层存储的方式是一样的,

不同的是

StringBuffer线程安全(现在很少使用),性能较差;StringBuilder线程不安全,但性能较好

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值