目录
- String底层解析
- 关于String
- String内部结构
- String的构造方法
- String中的对比——equals()和compareTo()的对比
- String的常用方法清单
- 关于equals()方法:“==”和equals()的区别?
- 为什么用final修饰String类?
- String和StringBuilder、StringBuffer的区别
- String两种创建方法的异同
- 编译器对String字符串的优化
关于String
String类型的底层代码是Java所有底层代码中相对来说比较简单、好理解。同时,String底层的源码又是面试官经典的“面试第一题”。“良好的开端就是成功的一半”,因此这个问题回答的好坏会很大程度影响你接下来的面试。我会在这篇博客中梳理几个面试中频率较高的知识点。
String内部结构
String内部存储结构为char数组,有两个私有变量,一个是char[],哈希值。
具体结构:
class String implements Serializable,Comparable,CharSequence {
//用于存储字符串的值
private char value[];
//缓字符串的hash code
private int hash;
}
String的构造方法
String的构造方法有四个,前两个比较常见,参数是String字符串和char[]数组。
后两个是容易被忽略的小透明,参数是StringBuffer和StringBuilder
1.String为参数的构造方法
//String为参数的构造方法
public String(String original){
this.value = original.value;
this.hash = original.hash;
}
2.char[] 为参数的构造方法
//char[]为参数的构造方法
public String(char value[]){
this.value = Arrays.copyOf(value,value.length);
}
3.StringBuffer为参数的构造方法
//StringBuffer为参数的构造方法
public String(StringBuffer buffer){
synchronized(buffer){
this.value=Array.copyOf(buffer.getValue(),buffer.length())
}
}
4.StringBuilder为参数的构造方法
//StirngBuilder为参数的构造方法
public String(StringBuilder builder){
this.value = Arrays.copyOf(builder.getValue(),builder.length());
}
String中的对比——equals()和compareTo()的对比
equals()方法
String中的equals()方法是重写了Object类中的equals()方法,其本质是利用了“==”。
equals()方法先检验对比双方的引用地址是否相等(“==”),如果地址相等,对比双方自然相等,返回true。然后检验需要对比的对象是否为String类型(“instanceof”),如果不是则返回false。之后对比两个字符串的长度是否对等(“==”),最后将两个字符串都转化为char[]形式,循环对比每一个字符。
源码:
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()方法
compareTo()方法不涉及地址的对比,它只是循环比较所有的字符串。当两个字符串中有任意一个字符不相同的时候,返回两个字符的差值。如果长度不对等则返回两个字符串长度的差值。
源码:
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
小结:String中compareTo()和equals()方法的异同点
不同点:
- equals()可以接收一个Object类型的参数,而compareTo()只能接收String类型的参数
- equals()返回值为Boolean,而compareTo()的返回值则为int
相同点:
- 两者都可以用于两个字符串的比较。当equals()方法返回true时,或是compareTo()方法返回0时,则表示两个字符串完全相同
String的常用方法清单
- indexOf():查询字符串首次出现的下标位置
- lastIndexOf():查询字符串最后出现的下标位置
- contains(): 查询字符串中是否包含另一个字符串
- toLowerCase(): 把字符串全部转换成小写
- toUpperCase(): 把字符串全部转换成大写
- length(): 查询字符串的长度
- trim(): 去掉字符串首尾空格
- replace():替换字符串中的某些字符
- split(): 把字符串分割并返回字符串数组
- join(): 把字符串数组转为字符串
关于equals()方法:“==”和equals()的区别?
“==”:
- 对于基本数据类型来说,是比较两者的值是否相等
- 对于引用数据类型来说,是用于比较两者引用地址是否相等
equals():
- String类型的equals()是重写了Object类中的equals()方法,他的基本实现是靠的“==”
Object类中的equal方法:
public boolean equals(Object obj){
return (this==obj);
}
为什么用final修饰String类?
对于这个问题,Java之父James Gosling在一次记者采访中说明过,大体的原因如下:
1.安全
迫使String类被设计成不可变类的一个原因就是安全。(在进行系统的校验工作中,如果设为可变类,就有可能会出现严重的系统崩溃问题。)
举例:字符串常量池
2.高效
高司令是这样回答的:他会更倾向于使用不可变类(final),因为它能够缓存结果,当你在传参时不需要考虑谁会修改它的值。如果是可变类的话,则有可能需要重新拷贝出来一个新值进行传参,这样性能上就会有一定的损失。
String和StringBuilder、StringBuffer的区别
首先,考虑到安全和性能问题,String类是不可变的,所以在字符串拼接的时候如果使用String的话性能会很低。因此就需要使用另一个数据类型StringBuffer。
StringBuffer:
- 它提供了append和insert方法可用于字符串的拼接
- 它使用了synchronized来保证线程安全
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的两种方式:
方式一:
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的优化。
举例:
String s1 = "ja" + "va";
String s2 = "java";
System.out.println(s1==s2); //true