String,StringBuffer,StringBuilder的浅析

清明节天气真是不错,有点小冷,但天晴的很,本来想出去走走(清明应该是除了五一回家外唯一一次有功夫出去的时候),但一想到大佬们都在学习,哪还能安心地玩耍,干脆就继续快乐图书馆吧。作为一个小白会的太少了,纠结了好久也不知道写什么,突然想起来挚爱的JAVA好久没用过了,所以这第一篇就说说JAVA中的String,StringBuffer,StringBuilder这三个类吧~

(清明节那天在微信上写的,忘发CSDN了...)

1. String——字符串常量

 

      众所周知Java中提供String类,通过对String类的对象来对字符串进行操作。来先让我们看看源码中怎么写的

String,StringBuffer,StringBuilder的浅析

String底层实际上是用了一个byte型数组来存储数据的.(jdk1.8及以前我记得是用char型数组存储,我用的是jdk11)  

 

而且我们可以看到byte数组被声明为final的,那么就说明了String类的对象是字符串常量,不能被修改。先来看段代码。

        String str="工藤新一";        System.out.println("str="+str);        str="怪盗基德";        System.out.println("str="+str);    

String,StringBuffer,StringBuilder的浅析

 

有人会给出上面这样的代码先构造一个String对象并初始化为“工藤新一”,接着用这个String类对象str再赋一个值“怪盗基德”,编译器没有报错,也能顺利运行str确实变了,于是他说String是可变的。那么事实真是这样的吗?“新机兹哇一自摸 hi 到次!”


 

我们先来写这样一段代码

        String str1="工藤新一";        String str2="工藤新一";        System.out.println(str1==str2);

 我们创造了两个String对象分别命名为str1和st2,它们均初始化为“工藤新一”,接着我们用==来判断两个对象的地址值(若要判断值相等应该用String类中重写的equals方法哦)

 

输出的结果竟然是 true。那么就意味着str1和str2实际上指的是内存中同一块区域。

 

来看图

String,StringBuffer,StringBuilder的浅析

JVM的设计者提供字符串常量池这样的一种东西来提高性能。

 

当我们以字面量形式(不同于new对象)实例化String对象时,该对象的值是保存在字符串常量池中,当我们再实例化另一个对象时,虚拟机首先会在常量池中寻找是否有相同的值,若有相同的值,则会将该区域地址赋给对象;若不存在则再开辟空间存储(常量池中不存在相同的字符串)。

        

由于声明str1时我们已经在常量池中写入了“工藤新一”,所以当我们另str2也赋值“工藤新一”时,虚拟机便让str2指向了和str1指向得相同的区域。因此我们会得到str1==str2为 true 的结果。好了可以回归正题了,看代码

        String str1="工藤新一";        String str2="工藤新一";        str1="怪盗基德";        System.out.println(str1==str2);

 当我们再把“怪盗基德”赋给str1,此时结果为false,那么这便说明了,前后两个str1其实并不是一个!“怪盗基德”是常量池中的另一块区域。


所以String对象是常量其实是没有问题的,当你给它赋新值的时候,本质上就是又创造了一个对象,会重新指定内存区域,只不过它们表面长成了一个样子而已。就像基德和新一的区别。


 

既然上面说到了常量池,我们再进一步说说这个内容,老规矩,先看代码

String,StringBuffer,StringBuilder的浅析

 

结果有没有很意外?str1==str2 结果为true,你已经知道为什么了,但其他的为什么为false呢?这还得看刚才那张图

 

String,StringBuffer,StringBuilder的浅析

 

这张图我想你很容易就看明白了为什么。当我们用字面量形式赋值时,JVM会在常量池中寻找,并进行操作;

 

而当我们用new+构造器的方式实例化的时候,JVM则会先在堆空间中开辟空间,再引用常量池中的地址。每次new都会在堆空间中开辟新的区域。

 

懂了吗亲?

 

自己再试试下面的代码,看看它们之间比较的结果

        String str1="工藤";        String str2="新一";        String str3="工藤新一";        String str4="工藤"+"新一";        String str5=str1+"新一";        String str6="工藤"+str2;        String str7=str1+str2;

 

2. StringBuffer——字符串变量

 

芜湖,总算开始第二部分了,技术文可真难写啊。String,StringBuffer,StringBuilder的浅析

先来瞧瞧StringBuffer的源码

String,StringBuffer,StringBuilder的浅析

 

我们可以看到StringBuffer从jdk1.0时就有了,可真是打娘胎里来的,我们可以看到它继承了一个抽象父类,让我们再进去看看。

 

String,StringBuffer,StringBuilder的浅析

 

原来StringBuffer底层也是用byte型数组存的,但我们注意到,这里可没有final修饰,和String那里是不一样的。那么StringBuffer是可变的。我们来看看StringBuffer的空参构造器源码

 

String,StringBuffer,StringBuilder的浅析

 

StringBuffer类提供append方法来追加数据

String,StringBuffer,StringBuilder的浅析

 

再进append方法,向底层追溯

String,StringBuffer,StringBuilder的浅析

我们发现有个ensureCapacityInternal函数,这个函数的作用便是判断现有容量是否足够的,继续追溯

 

String,StringBuffer,StringBuilder的浅析

是不是有点懵了,哈哈哈哈。

 

这里其实来说,就是如果数组现有长度不够了,那么便会给数组申请新的内存,并将原来的内容复制到新的数组中,再加上你要追加的数据。

 

那么申请的内存是多大的呢?让我们继续追溯

 

String,StringBuffer,StringBuilder的浅析

好了,现在我们已经很清楚了。简单来说,一般情况下,新申请的内存大小为原来大小的2倍再+2。当然还有特殊情况,这里就不赘述了哈哈哈。(这算不算手撕源码呢???)

 

那么,清楚了吗,亲?


 

 

3. StringBuilder——emmm...

 

其实StringBuilder和StringBuffer简直太像了,底层的存储结构,方法原理都是一样的,你看它们的父类都是AbstractStringBuilder,就是一个爹的亲兄弟啊!

String,StringBuffer,StringBuilder的浅析

String,StringBuffer,StringBuilder的浅析

 

那么它们有什么不同呢?接着看

String,StringBuffer,StringBuilder的浅析

String,StringBuffer,StringBuilder的浅析

 

我们拿append方法对比,可以发现StringBuffer的方法的前面有这样的一个关键字修饰——synchronized,而StringBuilder则没有

 

这就说明了,StringBuffer是线程安全的,而StringBuilder则线程不安全。

 

但有好处就有坏处,线程不安全就代表它快啊!效率高啊!这里就不给代码比较了。而且由于不需要每次都扩容,所以它俩都比String效率高得多。

 

有关String, StringBuffer, StringBuilder的细节内容还有好多好多,这里肯定是讲不完了,有兴趣还需要自己去发掘String,StringBuffer,StringBuilder的浅析


 

呜呼!累死我了,不过现在有点小成就感。也不知道有没有啥错误,欢迎大佬指正!下次更估计要好久之后了,下次写点啥呢?

上一篇:JavaScript基础——字符串


下一篇:KMP算法