零、Java 中 String 的源码
学习笔记来自
- 慕课网专栏 —— 面试官系统精讲 Java 源码及大厂真题
- 拉钩教育 —— Java面试真题及源码 34讲
一、开篇词
我们在学习 Java SE 的过程中,经常会用到这个类型,我们只会觉得用这个很方便。可以直接申明一个字符串类型。但是大家有没想过这个是怎么来的呢?
在学 C语言的时候,如果没有接触过 C++ 的同学,对于字符串处理的时候一般都是使用一个 char 类型的字符数组来解决。没错 String 的核心就是这样的。接下里我们来看一看 String 的源码是什么样子的呢?
二、String 类型的不可变性
我们写一个示例看看
package test;public class Test {public static void main(String[] args) {String a= "Hello";a = "wwww";System.out.println(a);}}
我们打上两个断点看一看。

从打断点的结果可以看出,当我们将字符串的值改为 www ,的时候,它的地址的引用就发生了改变,同时里面存储的字符也发生了变化,同时我们从打断点的结果看出,String 中的每个字符都是一个一个存储的。所以我们可以猜测两个结论
- String 被 final 修饰,是不能直接修改的,需要改变地址的引用才能换一个新的值
- String 中的字符使用字符数组存储的。
我们看下 String 的源码是什么样子的。
public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {/** The value is used for character storage. */private final char value[];
现在看看 String 的部分源码,和刚才猜测的相差不大
- String 是不可变类型,不可变就代表被 final 关键字修饰,也就是说 String 类不可能被继承,也就是说对 String 操作的方法,都不会被继承重写
- String 中保存数据的是一个 char 的数组 value。我们发现 value 也是被 final 修饰的,可以这么说,value 一旦被赋值,内存地址是绝对无法修改的,value 的访问修饰符是 private ,外部也访问不到,String 也没有提供对应的 setter 方法。因此 value 一旦产生,就无法被修改。
这两点就是使 String 不变性的原因,充分利用了 final 关键字的特性,以后在开发遇到这种情况,可以采取 String 源码的设计方式加到自己的项目里面。
我们看一个例子
public class Test {public static void main(String[] args) {String a= "Hello";a.replace("H","t"); // 这样写不会有效果,也不会有任何改变System.out.println(a);a = a.replace("H","t"); // 分析源码我们知道,我们对 String 的操作会引用一个新的地址,因此需要换一个新的 String 对象来接收。System.out.println(a);}}

三、String 中的方法
3.1 String 中的构造方法
有四类构造方法,需要我们注意的是后面两个构造方法,这两类一般都是单独使用的比较多
// 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 = Arrays.copyOf(buffer.getValue(), buffer.length());}}// StringBuilder 为参数的构造方法public String(StringBuilder builder) {this.value = Arrays.copyOf(builder.getValue(), builder.length());}
3.2 equals() 比较两个字符串是否相等
public boolean equals(Object anObject) {// 对象引用相同直接返回 trueif (this == anObject) {return true;}// 判断需要对比的值是否为 String 类型,如果不是则直接返回 false, instanceof 是用来判断数据类型的一个方法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;}
String 类型重写了 equals() 方法,equals() 方法需要传递一个 Object 类型参数值,因此比较字符串时,会先通过比较 instanceof 判断是否为 String 类型,如果不是直接返回 false
3.3 compareTo() 比较两个字符串
compareTo() 方法用于比较两个字符串,返回结果为 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) {// 比较单个字符,如果是字母字符,比较对应的 ASCLL 吗return c1 - c2;}k++;}// 长度不一样,直接返回长度之差return len1 - len2;}
从源码的返回值可以看出,compareTo() 会循环比较所有的字符,然后会取长度较短的字符依次循环遍历每一个字符,如果有不相等的,则返回 return char1 - char2, char2 使我们从方法外面传进来的 字符串,方法一是被比较的字符串,两者相减得到的值就是返回值。
由此可以看出 compareTo() 和 equals() 方法的区别:
- equals() 可以接受一个 Object 类型参数,而 compareTo() 只能接收一个 String 参数。
- equals() 返回 Boolean 值,compareTo() 返回 int
两者都用于字符串比较,当 equals() 为 true,compareTo() 为 0 的时候,两者功能是一样的。都表示同一个字符串
3.4 String 中常用的其他方法
public static void main(String[] args) {String str = "adminc123332aa";System.out.println(str.indexOf("12")); // int 返回子字符串第一次出现的下标位置System.out.println(str.lastIndexOf("a")); // int 返回子字符串最后一次出现的下标位置System.out.println(str.contains("admin")); // boolean 判断字符串是否存在String str1 = "ABCDEFG";System.out.println(str.toLowerCase()); // 将字符串全部大写转换为小写System.out.println(str.toUpperCase()); // 将字符串中全部小写转换为大写System.out.println(str.length()); // 获得字符串的长度String str2 = " aa ";System.out.println(str.trim()); // 去掉字符串首尾空格System.out.println(str.replace("admin","cococ")); // 替换字符串数组的某些字符// 分割字符串,返回一个字符串数组String[] chrs = str.split("");System.out.println(chrs[0]);}
四、面试询问
为什么 String 要用 final 修饰?
使用 final 修饰表明 String 是不可继承的类,这样做表示更安全,更高效
== 和 equals 的区别是什么?
- == 对于基本数据类型来说,是比较 “值” 相等的,对于引用类型来说,是比较引用地址是否相同
- String 重写了 equals 方法把它变成了两个字符串是否相等
- String 和 StringBuilder、StringBuffer 有什么区别?
