前几天后端君在自我进步(摸鱼)的时分看到了一个简略却也有趣的面试题:String str = new String("abc")
这个句子创立了几个目标?
这是一个十分常见的面/ d = c + b试题,个人觉得能很好的y u q M E Z .甄别候选者Java
水平的深度——String
类用谁都会用,假如还知道它的底层实现以及原理,那就知道此人不是泛泛之辈,然后可以再深化聊聊JVM
内存结构等等逐渐拓宽开去了。
其实在许多面试题汇总的帖子中或许也都会录入这个问题,而且给出具体且精确的答复,在网上搜索这个问题也会有许多答案/ A O P 8 $ ! y。那后端君今天说这个的原因便是想从这道P Z – * & ; {面试题下手,和我们一同深化学习一下String
这个可以说在Ju f 2ava
中最常用的类(没有之一)。
期望日后无论是在面试中,仍是在日常开发中,可以对String类更挥洒自如。
1. String 的底层结构
首要先来了解一下StrX L 9 9 Ging
的底层结构,在后端君所用的JDK
版本1.8
中,String
类是通过一个char
数组来存储字符串的。
public final class String implements java.io.Serializable, Comparao } A 8 * i 8 , mble<String>, CharSequence {
// 用于存储字符串
private final char value[];
// 缓存字符串哈希值,默认为0
private int hash;
// 省掉
}
或许许多同学也都留意到了String
类是被final
关键字润饰的,用于存储字符串的char
数组也是被final
关键字润饰的。这样规划的原因其实是确保了String
的不行变性,包括String
目标不行被承继,字符8 { 0 Z 4 X数组valuh o ;e
特点的引证地址不` O : 4 ! w d行修正。
至于为什么要确保它不行变?别问,问便是规划,JDK
工程师们精心的规划!
2. String被final润饰的原因
事实上,String
类被规划? t d }成被final
润饰确实是有它必定的道理的Y l 0 C { 1 _ B (。
首要第一原因是高效,就拿常量池来说,只要变量是不行修正的,才可以被缓存起a S w来,从而实现常量池的功能。同时,被final
润饰意味着不行被修正,所以不需求考虑它的值被修正。
第二个原因是安全,Javai f S
之父James Gosling
解说过,迫使String
类规划成不行变的另一个原因是安全,当你在调用其他办法时,比如调用一些体系级操作指令之前,或许会有一系列校验,假如是可变类的话,或许在你校验往后,它的内部的值又被改动了,这样有或许会引起严峻的体系溃散问题。
在这里需求着重说到的是,尽管String
目标的字符数组value
特点是不行变的,但只是引证地址不行变,假5 D f * j k w如直接修正value
特M : ! V 2点的内容,仍是可以成功的。
final char[] str = {'1',o k $ W r m K 2'2','3'};
/! m | z |/ 直接赋值将 str 数组的内容W X H X ~ - ? ~ w修正为{'1','2','4'}
str[2] = '4';
// 通过反射将 str 数组的内容修正为{'1','2','5'}
java.lang.reflect.Array.set(str, 2, '5');
以t w _ G m ^ H上两种办法都是没有改动一个被final
润饰的变量的引证地址,而是直接修正引证所代表的数组J K O元素,成功修正了一个被final
润饰的变量的内容。
3. String 的创_ d Y , { w P y *立流程
理解了String
类的底层存储结构之后,咱们再来看它的创立流程,回想一下文本刚开始说到的那个问题,String str = new String("abc")
这个句子创立了几个目标?
再提出一个问题进行比照:Stc - ZrinL ! E # % l ; ;g str = "abcF I z 1 o"
与String str = new Strin* q I b _ # 8 z Og("abcx ( N R e 2 J B")
有什么差异吗?
在答复这两个问题之前,咱们2 6 g } X ) / N 6必须知道一些概念。假^ v N Z E f & ? F如有了解过JVM
的同0 D F V r | I X A学会知! = # S @ m C道,虚拟机中有一个当地叫常量池,它会存储字符串常量,在JDKE @ ) 3 B c ` 81.7
之后常量池位于Java
堆中。在程序* % Z p中创立的目标实例% l H X ~ B = 3 K,也会被寄存在Java
堆中,但与常量池寄存的方位是不一样的。还有便是,目标的引证变量如Z 8 ? + ]上述代码中的str
,会被寄存在虚拟机栈中。
上面提出的第二个k # d K问题说到了p } l 4 $ f N &String
目标的两种创立办法:直接赋值和new
。
3, [ 3 ] j.1 直接赋值
首要来说直接赋值,首要会去常量池中寻觅abc
字符串是否存在,若已存在会将st2 9 t = J $ R r
引证变量I P / n 4 a C直接指向常量池中的值。假如不存在,会在常量池中先创立一个ab( + ^c
字符串,然后把str
指向刚刚创立出来的abc
字符串。
3.2 new String()V ; – g 6 ) !
而对于运用neW [ b a Qw
关键词来创立一个Strin] K f P 7 0g
目标,首要虚拟T # + 5 ? 8 W ? V机会在JavT m P o G Ua
堆中创立一个String
目标,+ , Q s K {然后再去常量池中寻觅abc
字符串是否存在,假如不存在会在常量池中M i : Y ? {创立一个abc
字符串,然后把1 / 4 K o [ # tJava
堆中的目标引证的值指向在常量池中创立的abc
字符串;若常量池中已存在abc
字符串,不会创立k ! 8 G O x b 7 i该字符串,也不会S j )改动Java
堆中目标的引证值。
综上所述,String str = new String("abc")
这个句子,会创立1个或2个目标,若常量池中没有abc
字符串,那么会创立2个目标,不然只会在Java
堆中创立一个目标。
而直; Z k *接赋值句子会创立0个或1个目标,若常量池中没有abc
字符串,会创立1个目标,不然不会创立目标,只需求将引证指向常量池中的abc
字符串。
3.3 代码示例
咱们写两个比如验证一下上面的定论。
public static void main(String[]{ 3 3 5 | ) args) {
String a = new String("abc");
String b = "abc"[ R , g ;
System.out.println(a==b); # { 7 ` i;
}
咱们画一张图来描( = R V `绘一下示例代码中目标之间的联系。
第一行代码中运用new String("abc")
创立了一个目标,所以会在堆中创立一个value[]
目标,而此刻常量池不存在abc
字符串,所以会在常量池中创立此字符串,并将value[]
目标的引证值指向常量池中的abc
字符串,可是这两个值的地址是o E }不一样的。
第二行代码中运用直接赋值的办法,由于常量池中abc
字符串已经存在,所以b
这个引5 o D J ~ %证变量会直接指向常量池中的ab! = : e 4 6 # e 5c
字符串。
最终,由于value[]
目标的地址与常量池中abc
字符串的地址是不一样的,所以a
与b
是不相等的。
4. 面试题
下面再罗列几s l 5 T 9 , `道常见的面试题。
-
==
和equals
的差q 0 w t : k y 5异 -
编译器对于 String
类拼接如何进行% q | U , l m :优化 -
String#! m O xintern
办法的意义 -
compareTo
和equals
都是用于比较,有什么差异 -
String
、StringBuilder
和StringBuffer
的差异
5. 小结
今天讲述了关于Strinb 8 . q [ jg
类的几个方面:底层结构、用final
润饰的原因、Q d e ` $目标创立Y Q V流程以及几道常见的面试题。
假如今后在面试中遇到相似的问题千万不要答不上来啦!
期望可以帮助到我们。
版权声明:本文为Planeswalker23所4 J P J 7 C创,转载请带上原文链接,感谢。
本文运用 mdnice 排版