本篇包含内容:
面试题:String,StringBuffer,StringBuilder的区别?
String常见问题解析:
①String特点?
②为什么String长度不可变?
③为什么String不是基本数据类型,但是可以不用new创建对象?
⑤String两种创建方式(String a="abc"和 String a=new String("abc"))区别?
⑥String的拼接问题
⑦String的equals和==
⑧String设计成不可变的原因?
StringBuilder源码解析
①StringBuilder为什么可变?
②StringBuilder扩容方式?
StringBuffer源码解析
面试常见问题:String,StringBuffer,StringBuilder的区别?
这个问题的答案:
1,从运行速度上看:StringBuilder > StringBuffer > String
2,从长度上看:StringBuilder 和StringBuffer 是字符串变量,长度可变,String 创建后不可更改
3,从线程安全上看:StringBuilder是线程不安全的,StringBuffer是线程安全的
4,结论:
当操作String时,需要不断创建和回收对象
String 适合少量字符串操作情况
StringBuffer:适用于多线程在字符缓冲区进行大量操作情况
StringBuilder:适用于在单线程在字符缓冲区进行大量操作的情况
如果你是一个刚学java的小白,只是为了应付下面试,把上面的答案背一背就可以了。
如果你被生活所迫 发自内心想了解更多,请继续往下看(以下会涉及到栈丶堆相关内容,不在本篇文章重点讲述,如还不了解,请自行百度)
------------------------------------------------------重点之String--------------------------------------------------
1,String特点?
①String不可继承,支持序列化,支持自然排序
看源码:
String 是由final修饰的类,且实现了Serializable和Comparable接口
②String可以采用直接赋值的形式进行操作,这一点像基本数据类型的赋值操作一样。范例:String str = "hello";
③String1.equals(String2)比较的是两个String字符串的值,一般类是比较的引用
④String底层实现用的char[] 数组
⑤String类的设计使用了一个共享设计模式。
有的看官老爷就说了:弟弟,能说点人话吗?
哥哥,别急呀(手动撒娇)
在JVM中,为了减少字符串对象的重复创建,维护了一块特殊的内存空间,这块内存就被称为字符串常量池。为啥要维护这么一块空间,还是因为String常用啊,因为常用,所以难免会有相同的字符串,没有必要为相同的字符串浪费两块内存,如果设计成共享的,那么会节省资源。
⑥字符串一旦创建,对他任何操作都不能改变
2,为什么String长度不可变?
看源码:
String 类是通过char数组保存字符串的,且char数组也是由final修饰的,final修饰的变量,不可更改所以长度固定的
3,为什么String不是基本数据类型,但是可以不用new创建对象?
上边我们说了一个概念:字符串常量池
那么什么是字符串常量池呢?
( 额...固定搭配,背下来....)
开玩笑啦,其实你可以把字符串常量池当做图书馆,就是里边书都是公用的共享的,你想用你拿去用,他想用拿去用,一万个人都想用这本书,都可以拿去用,但是书只需要买一本就够了,这样可以大大节省资源,省下钱做啥不好。字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价。JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池。
了解这些,这个问题原因就很容易理解了。每当代码创建字符串常量时,JVM会首先检查字符串常量池。如果字符串已经存在池中, 就返回池中的实例引用。如果字符串不在池中,就会实例化一个字符串并放到池中。所以String虽然是引用类型,依然可以不用new创建对象,说白了还是这小伙子常用呗。
4,String两种创建方式(String a="abc"和 String a=new String("abc"))区别?
看官老爷又发话了:您可真是傻,这区别还不明显吗?一个用new一个不用new,就这?
要是这么简单,我怎么好意思在各位聪明的看官老爷面前显摆呢?
第一种方式,先去常量池中遍历,如果有"abc"字符串,则直接返回当前地址,如果没有,则先在常量池中创建"abc",然后返回其地址,此过程创建零个或一个对象
第二种方式,先在池中遍历,,如果有"abc"字符串,则进行下一步,如果没有,则先在常量池中创建"abc",然后在堆中创建String对象,指向池中"abc"的地址,此过程创建一个或两个对象,一定会创建对象
看不懂?没关系,
我们通过一个笔试题来看String的创建过程(敲黑板,划重点,常考):
String s1="javaEE";
String s2="javaEE";
String s3=new String("javaEE");
String s4=new String("javaEE");
System.out.println(s1==s2);
System.out.println(s1==s3);
System.out.println(s1==s4);
System.out.println(s3==s4);
字符串s1,s2,s3和s4的创建过程是什么样的呢?看图
s1:局部变量是存在栈中的,所以当执行到String s1这一步时,会在栈内存中开辟空间,存放s1,下一步s1="javaEE", 如果是通过字符串这种直接赋值的形式,会先检查字符串常量池是否有"javaEE"这个字符串,因为这是第一次创建,在字符串常量池中不存在,所以先在常量池中创建"javaEE"对象,这里假设"javaEE"的地址是0x1212,s1中存放的地址是0x1212;
s2:与s1创建过程一样,先在栈中存放s2,然后去常量池中找是否有"javaEE"字符串,创建s1时已经创建过了,所以直接返回此地址0x1212,那么s2的地址与s1相同
s3:同样先执行String s3,在栈中开辟内存存放s3,下一步s3=new String();要在堆中开辟一块内存存放String对象(此时假设对象地址是0x8899),所以s3中存放地址为0x8899,然后将常量池中对应字符串的地址赋值给堆中String对象,那么String 的value值为0x1212;
s4:与s3过程相同,只是new对象肯定会重新创建对象,所以s4的地址值不能指向0x8899,而是0x7777;
讲到这里,答案就很明显了,就第一个为true;
5,String的拼接问题
①两个字符串直接拼接,String a="a"+"b";使用最多的情况就是字符串太长,一行放不下,需要拼接,这种是直接在常量池中创建"ab",编译期间就执行完毕了,效率高
②对于 String a="a";String b="b"; a+b ;的情况则是运行期执行,反编译后:new StringBuilder(String.valueOf(a))).append(b).toString(); 此时在堆中创建两个对象
③String的concat(String str) 方法,每次调用都会重新在常量池中创建新对象,并将新对象地址赋给String,代码实现:创建char数组,长度是两个字符串长度和,将两个字符串复制到数组中,并创建新对象返回,所以调用几次,创建几个对象
6,String的equals和==
默认情况下equals和==都是比较地址,但是String重写了equals方法,所以String比较的是值,看一下equals()方法源码:
源码分析:
1,若A==B,则是同一个String对象,返回true
2,若对象都是String类型继续下一步,否则返回false
3,判断A,B长度是否一样,不一样返回false
4,逐个字符比较,若有不同,返回false
equals的特点:
自反性:x.equals(x)一定返回true
对称性:x.equals(y)为true,y.equals(x)一定为true
传递性:x.equals(y)=true,y.equals(z)=true,那么x.equals(z)=true
一致性:如果参与比较对象没变,对象比较结果也不会变
非空性:任何非空引用值x,x.equals(null)一定为false
7,String设计成不可变的原因?
面试时或许会问到,还能有啥原因?常用呗(小声bb)。当然肯定不能回答这个
①字符串常量池的需要,字符串常量池是为了提升效率和减少内存分配,编程大量时间都在处理字符串,所以字符串很大概率出现重复情况,String不可变,常量池更容易被管理和优化.
②安全性考虑,因为字符串广泛用于参数,如网络连接,打开文件等,如果String可变,网络连接等将被改变,导致一系列安全问题
③java中常用到字符串的hash码,String不变,hash码唯一,不用每次用的时候重新计算,提高效率
--------------------------------------------------------StringBuilder----------------------------------------------------
1,StringBuilder的源码分析(为什么长度可变?)
先来看默认构造方法:
默认构造方法调用了父类构造方法,我们点进去看看如下图:
父类构造方法创建一个长度为16的char数组,原来StringBuilder用的也是char数组,那看官老爷就要问了,为什么String用的char数组,长度不可变,你也用char数组你就能变,就因为你名长吗?当然不是,StringBuilder的char数组不是final修饰的,所以它是可以被重新赋值的。看官老爷可能一脸懵逼,咋回事?恩...我也迷糊了,那我们先写个测试代码,然后调用拼接方法试试吧
先创建个StringBuilder对象sb,恩...不太好听(成大事不拘小节),然后调用append方法拼接,并没有重新给sb赋值,但是sb的内容变了,很神奇。构造方法没有什么特殊的,那点开append方法看一看:
调用父类append()方法,并返回当前对象,点父类append()看看具体做了哪些事情:
先看str.getChars()这行代码,是将str的字符复制到value数组中,从value数组的count处存放,说白了就是将str字符拼接到原字符的后边,那么我们知道StringBuilder是通过char[] value存储的,最初长度是16位,如果长度超过16位,会如何处理呢?ensureCapacityInternal()方法就是处理扩容问题的,我们点进去看一看:
minimumCapacity是拼接后字符串长度,value.length是存放当前字符串的数组长度,如果拼接后长度大于当前数组长度,则将当前数组拷贝到新数组并赋值给当前value。至此,我们应该知道为什么StringBuilder是可变的,先创建一个16长度数组,拼接内容就往数组里添加字符,当超过16就给value赋值新的char数组。这时会有人有疑问,那我新创建数组到底创建多大的呢?既然要扩容我最开始创建个很大的不就好了,省的这么麻烦?别急,慢慢看,具体如何扩容呢,新数组多长呢,秘密全在newCapacity()这个方法中,我们先点进去看看:
第一行将当前数组容量*2再加2,如果新的容量比拼接后的字符串长度要小,那直接将新容量改为拼接后的字符串长度.最后一行,当新容量小于等于0或者大于MAX_ARRAY_SIZE,基本很少发生,故先不考虑。
--------------------------------------------------------StringBuffer----------------------------------------------------
StringBuffer内容与StringBuilder基本相同,扩容方式一样,唯一不同的是StringBuffer的拼接方法是同步的,看下图,所以是线程安全的
能看到这里,我发现你是真的爱我,那我也想对你说一句:
把耳朵伸过来
.
.
.
.
.
.
.
如果有用,麻烦点个赞呗,软件行业的小学生,Android上的弟弟在这里跪谢了
END