String类型的底层代码是Java全部底层代码中相对来讲比较简单、好理解。同时,String底层的源码又是面试官经典的“面试第一题”。“良好的开端就是成功的一半”,所以这个问题回答的好坏会很大程度影响你接下来的面试。我会在这篇博客中梳理几个面试中频率较高的知识点。java
String内部存储结构为char数组,有两个私有变量,一个是char[],哈希值。
具体结构:面试
class String implements Serializable,Comparable<String>,CharSequence { //用于存储字符串的值 private char value[]; //缓字符串的hash code private int hash; }
String的构造方法有四个,前两个比较常见,参数是String字符串和char[]数组。
后两个是容易被忽略的小透明,参数是StringBuffer和StringBuilder数组
//String为参数的构造方法 public String(String original){ this.value = original.value; this.hash = original.hash; }
//char[]为参数的构造方法 public String(char value[]){ this.value = Arrays.copyOf(value,value.length); }
//StringBuffer为参数的构造方法 public String(StringBuffer buffer){ synchronized(buffer){ this.value=Array.copyOf(buffer.getValue(),buffer.length()) } }
//StirngBuilder为参数的构造方法 public String(StringBuilder builder){ this.value = Arrays.copyOf(builder.getValue(),builder.length()); }
String中的equals()方法是重写了Object类中的equals()方法,其本质是利用了“==”。缓存
equals()方法先检验对比双方的引用地址是否相等(“==”),若是地址相等,对比双方天然相等,返回true。而后检验须要对比的对象是否为String类型(“instanceof”),若是不是则返回false。以后对比两个字符串的长度是否对等(“==”),最后将两个字符串都转化为char[]形式,循环对比每个字符。安全
源码:app
public boolean equals(Object anObject){ //对象引用相同直接返回true if(this==anObject){ return true; } //判断须要对比的值是否为String类型,若是不是则返回false if(anObject instanceof String){ String anotherString = (String)anObject; int n = value.length; if(n==anotherString.value.length){ //把两个字符串都转化为char数组对比 char v1[]=value; char v2[]=anotherString.value; int i=0; //循环比对两个字符串的每个字符 while(n--!=0){ //若是其中有一个字符不相等就true false,不然继续对比 if(v1[i]!=v2[i]) return false; i++; } return true; } } return false; }
compareTo()方法不涉及地址的对比,它只是循环比较全部的字符串。当两个字符串中有任意一个字符不相同的时候,返回两个字符的差值。若是长度不对等则返回两个字符串长度的差值。jvm
源码:性能
public int compareTo(String anotherString){ int len1 = value.length; int len2 = anotherString.value.length; //获取到两个字符串长度最短的那个int值 int lim = Math.min(len1,len2); char v1[]=value; char v2[]=anotherString.value; int k=0; //对比每个字符 while(k<lim){ char c1=v1[k]; char c2=v2[k]; if(c1!=c2){ //有字符不相等就返回差值 return c1-c2; } k++; } //检验长度 return len1-len2; }
不一样点:优化
相同点:ui
“==”:
equals():
Object类中的equal方法:
public boolean equals(Object obj){ return (this==obj); }
对于这个问题,Java之父James Gosling在一次记者采访中说明过,大致的缘由以下:
1.安全
迫使String类被设计成不可变类的一个缘由就是安全。(在进行系统的校验工做中,若是设为可变类,就有可能会出现严重的系统崩溃问题。)
举例:字符串常量池
2.高效
高司令是这样回答的:他会更倾向于使用不可变类(final),由于它可以缓存结果,当你在传参时不须要考虑谁会修改它的值。若是是可变类的话,则有可能须要从新拷贝出来一个新值进行传参,这样性能上就会有必定的损失。
首先,考虑到安全和性能问题,String类是不可变的,因此在字符串拼接的时候若是使用String的话性能会很低。所以就须要使用另外一个数据类型StringBuffer。
StringBuffer:
StringBuffer中append()方法:
public synchronized StringBuffer append(Object obj){ toStringCache = null; super.append(String.valueOf(obj)); return this; } public synchronized StringBuffer append(String str){ toStringCache = null; super.append(String.valueOf(str)); return this; }
可是因为StringBuffer保证了线程的安全,因此性能上来说 —— 很低。
因而在JDK1.5中推出了线程不安全,可是性能相对而言较高的StringBuilder。其功能和StringBuffer同样。
咱们先来看看建立String的两种方式:
方式一:
String s1 = "java"; //直接赋值
方式二:
String s2 = new String("java"); //对象建立
这两种方法均可以建立字符串,可是两个方法在JVM的存储区域大相径庭
方法一:
jvm会先到字符串常量池中寻找是否有相同的字符串,若是有就返回常量句柄;若是没有该字符串,就先在常量池中建立此字符串,而后再返回常量句柄。
方法二:
直接在堆中建立此字符串,只有调用intern()才会放入字符串常量池中。
举例:
String s1 = new String("java"); String s2 = s1.intern(); String s3 = "java"; System.out.println(s1==s2); //false System.out.println(s2==s3); //true
(s2,s3指向堆中常量池的“java”,s1指向堆中的“java”对象)
== 补充:jdk1.7以后把永生代换成了元空间,把字符串常量池从方法区移到了java堆上 ==
咱们常常会用“+”来拼接两个字符串。即便是这样拼接的字符串,在进行比较时,也不会出现和结果相同字符串不对等的状况。这是编译器对于String的优化。
举例:
String s1 = "ja" + "va"; String s2 = "java"; System.out.println(s1==s2); //true