浅谈String的特点和具体的源码实现
1、String源码本质
String的基本使用是Java入门的一个必修课,在面试中有时候也往往会是第一道面试题,一些互联网大厂也喜欢从最基础的知识点入手,然后追问技术实现细节。所以本博客通过源码和对比方式对一些实现细节简单分析
以jdk8版本的源码来说,String是以final修饰的类,实际存储的数据结构为char类型的数组
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
private int hash;
private static final long serialVersionUID = -6849794470754667710L;
...
}
2、构造方法
挑出String 里的主要几个构造方法,String 不仅可以传入String 参数、char 数组,而且可以传入StringBuffer 和StringBuilder
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
3、equals(Object)方法
equals(Object) 方法比较两个字符串是否相等。String 类重写了Object 类的equals(Object) 方法,比较的是字符串每个字符。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
equals(Object) 方法传入一个Object 对象,会用instanceof 判断是否为String 类型,不是String 类型直接返回FALSE
Object oStr = "123";
Object oInt = 123;
System.out.println(oStr instanceof String);
System.out.println(oInt instanceof String);
equals(Object) 方法注释里,作者也提到了两个类似方法,@see #compareTo(String) @see #equalsIgnoreCase(String) ,equalsIgnoreCase(String) 方法用于忽略字符串的大小写之后的字符串比较,compareTo(String) 也是用于字符串比较的,具体和equals(Object) 有什么不同?
4、compareTo(String)方法
compareTo(String) :用于比较两个字符串,返回的结果为 int 类型的值
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
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;
}
从源码也可以看出compareTo(String) 方法也是遍历字符串的每一个字符,当两个字符串中有任意字符不相同时,返回c1 - c2 。比如,两个字符串分别存储的是 1 和 2,返回值-1;如果两个字符串存储的是1和1,返回值0,;如果两个字符串存储的是2和1,返回值是1
和equals(String) 方法一样,compareTo(String) 方法也有一个忽略大小写的比较方法compareToIgnoreCase(String) ,compareToIgnoreCase(String) 用于用于忽略大小写后比较两个字符串。
5、compareTo(String) 和equals(Object) 对比
compareTo(String) 方法和 equals(Object) 方法,这两个方法都可以用于比较字符串
- compareTo(String) 方法和 equals(Object) 方法的区别:
- equals() 可以接收一个 Object 类型的参数,而 compareTo() 只能接收一个 String 类型的参数
- equals() 返回值为 Boolean,而 compareTo() 的返回值则为 int。
6、其他方法
- indexOf():查询字符串首次出现的下标位置
- lastIndexOf():查询字符串最后出现的下标位置
- contains():查询字符串中是否包含另一个字符串
- toLowerCase():把字符串全部转换成小写
- toUpperCase():把字符串全部转换成大写
- length():查询字符串的长度
- trim():去掉字符串首尾的空格
- replace():替换字符串中的某些字符
- split():把字符串按分隔符分割,返回字符串数组
- join():把字符串数组转为字符串
拓展知识
上面对String的常用方法做了一个比较简单的介绍,下面给出面试中一个很常见的面试题,进行介绍,主要是学习理解,并非为了面试而面试
- 为什么 String 类型要用 final 修饰?
final 修饰的类都是一个不可继承类。那这样设计有什么好处呢?
Java 语言之父 James Gosling的回答是,他会更倾向于使用final,因为它能够缓存结果,当你在传参时不需要考虑谁会修改它的值;如果是可变类的话,则有可能需要重新拷贝出来一个新值进行传参,这样在性能上就会有一定的损失。 另一个原因是安全,当你在调用其他方法时,比如调用一些系统级操作指令之前,可能会有一系列校验,如果是可变类的话,可能在你校验过后,它的内部的值又被改变了,这样有可能会引起严重的系统崩溃问题,这是迫使 String 类设计成不可变类的一个重要原因。
总而言之就是用两个优点,一个是安全,另外一个是性能问题
- == 和 equals 的区别是什么?
- ==:对比的是栈中的值,基本数据类型对比的是变量值,引用数据类型对比的是堆中内存对象的地址
- equals:Object中默认也是常用==进行比较,而String的equals进行重写,比较的是两个字符串的内容
- String 和 StringBuilder、StringBuffer 有什么区别?
- String是不可变的,如果尝试修改String字符串,会重新生成一个对象;而StringBuffer、StringBuilder是可变的
- StringBuffer是线程安全的,StringBuilder是线程不安全的,所以StringBuilder的执行效率要快于StringBuffer
- String 的 intern() 方法有什么含义?
public static void main(String[] args) {
String s1 = new String("abc");
String s2 = "abc";
String s3 = s1.intern();
}
答案:
- s1 == s2 false
- s2 == s2 true
String 的intern() 方法,会检查字符串常量池中是否有“abc”字符串,如果有,返回改字符串引用,否,将“abc”添加到常量池中。
详情可以参考博客深入解析String#intern 美团技术团队
- String和new String两张创建方式有什么区别?
- String str1 = “abc”; 最多创建一个String对象,最少是不创建对象。如果常量池中有“abc”,那么str1直接引用就可以,否则创建“abc”的内存空间,如何再引用。引号创建的字符串是直接量,在编译器就会存储到常量池中
- String str2 = new String(“abc”);最多创建两个对象,最少创建1个对象。new关键字,会在堆创建内存区域,在运行期才创建。
|