清明节天气真是不错,有点小冷,但天晴的很,本来想出去走走(清明应该是除了五一回家外唯一一次有功夫出去的时候),但一想到大佬们都在学习,哪还能安心地玩耍,干脆就继续快乐图书馆吧。作为一个小白会的太少了,纠结了好久也不知道写什么,突然想起来挚爱的JAVA好久没用过了,所以这第一篇就说说JAVA中的String,StringBuffer,StringBuilder这三个类吧~
(清明节那天在微信上写的,忘发CSDN了...)
1. String——字符串常量
众所周知Java中提供String类,通过对String类的对象来对字符串进行操作。来先让我们看看源码中怎么写的
String底层实际上是用了一个byte型数组来存储数据的.(jdk1.8及以前我记得是用char型数组存储,我用的是jdk11)
而且我们可以看到byte数组被声明为final的,那么就说明了String类的对象是字符串常量,不能被修改。先来看段代码。
String str="工藤新一";
System.out.println("str="+str);
str="怪盗基德";
System.out.println("str="+str);
有人会给出上面这样的代码先构造一个String对象并初始化为“工藤新一”,接着用这个String类对象str再赋一个值“怪盗基德”,编译器没有报错,也能顺利运行str确实变了,于是他说String是可变的。那么事实真是这样的吗?“新机兹哇一自摸 hi 到次!”
我们先来写这样一段代码
String str1="工藤新一";
String str2="工藤新一";
System.out.println(str1==str2);
我们创造了两个String对象分别命名为str1和st2,它们均初始化为“工藤新一”,接着我们用==来判断两个对象的地址值(若要判断值相等应该用String类中重写的equals方法哦)
输出的结果竟然是 true。那么就意味着str1和str2实际上指的是内存中同一块区域。
来看图
JVM的设计者提供字符串常量池这样的一种东西来提高性能。
当我们以字面量形式(不同于new对象)实例化String对象时,该对象的值是保存在字符串常量池中,当我们再实例化另一个对象时,虚拟机首先会在常量池中寻找是否有相同的值,若有相同的值,则会将该区域地址赋给对象;若不存在则再开辟空间存储(常量池中不存在相同的字符串)。
由于声明str1时我们已经在常量池中写入了“工藤新一”,所以当我们另str2也赋值“工藤新一”时,虚拟机便让str2指向了和str1指向得相同的区域。因此我们会得到str1==str2为 true 的结果。好了可以回归正题了,看代码
String str1="工藤新一";
String str2="工藤新一";
str1="怪盗基德";
System.out.println(str1==str2);
当我们再把“怪盗基德”赋给str1,此时结果为false,那么这便说明了,前后两个str1其实并不是一个!“怪盗基德”是常量池中的另一块区域。
所以String对象是常量其实是没有问题的,当你给它赋新值的时候,本质上就是又创造了一个对象,会重新指定内存区域,只不过它们表面长成了一个样子而已。就像基德和新一的区别。
既然上面说到了常量池,我们再进一步说说这个内容,老规矩,先看代码
结果有没有很意外?str1==str2 结果为true,你已经知道为什么了,但其他的为什么为false呢?这还得看刚才那张图
这张图我想你很容易就看明白了为什么。当我们用字面量形式赋值时,JVM会在常量池中寻找,并进行操作;
而当我们用new+构造器的方式实例化的时候,JVM则会先在堆空间中开辟空间,再引用常量池中的地址。每次new都会在堆空间中开辟新的区域。
懂了吗亲?
自己再试试下面的代码,看看它们之间比较的结果
String str1="工藤";
String str2="新一";
String str3="工藤新一";
String str4="工藤"+"新一";
String str5=str1+"新一";
String str6="工藤"+str2;
String str7=str1+str2;
2. StringBuffer——字符串变量
芜湖,总算开始第二部分了,技术文可真难写啊。
先来瞧瞧StringBuffer的源码
我们可以看到StringBuffer从jdk1.0时就有了,可真是打娘胎里来的,我们可以看到它继承了一个抽象父类,让我们再进去看看。
原来StringBuffer底层也是用byte型数组存的,但我们注意到,这里可没有final修饰,和String那里是不一样的。那么StringBuffer是可变的。我们来看看StringBuffer的空参构造器源码
StringBuffer类提供append方法来追加数据
再进append方法,向底层追溯
我们发现有个ensureCapacityInternal函数,这个函数的作用便是判断现有容量是否足够的,继续追溯
是不是有点懵了,哈哈哈哈。
这里其实来说,就是如果数组现有长度不够了,那么便会给数组申请新的内存,并将原来的内容复制到新的数组中,再加上你要追加的数据。
那么申请的内存是多大的呢?让我们继续追溯
好了,现在我们已经很清楚了。简单来说,一般情况下,新申请的内存大小为原来大小的2倍再+2。当然还有特殊情况,这里就不赘述了哈哈哈。(这算不算手撕源码呢???)
那么,清楚了吗,亲?
3. StringBuilder——emmm...
其实StringBuilder和StringBuffer简直太像了,底层的存储结构,方法原理都是一样的,你看它们的父类都是AbstractStringBuilder,就是一个爹的亲兄弟啊!
那么它们有什么不同呢?接着看
我们拿append方法对比,可以发现StringBuffer的方法的前面有这样的一个关键字修饰——synchronized,而StringBuilder则没有
这就说明了,StringBuffer是线程安全的,而StringBuilder则线程不安全。
但有好处就有坏处,线程不安全就代表它快啊!效率高啊!这里就不给代码比较了。而且由于不需要每次都扩容,所以它俩都比String效率高得多。
有关String, StringBuffer, StringBuilder的细节内容还有好多好多,这里肯定是讲不完了,有兴趣还需要自己去发掘。
呜呼!累死我了,不过现在有点小成就感。也不知道有没有啥错误,欢迎大佬指正!下次更估计要好久之后了,下次写点啥呢?