JDK1.8-java.lang.String类源码阅读

String类应该是我们用的最多的一个类了。String类的方法很多,每个都写太麻烦了,也没有必要。我就捡我觉得比较重要的和应该注意的点写了。

1、定义

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence

可以看到有final修饰,说明String是不可变的,实现了Serializable、Comparable接口,说明String可以序列化,可以进行比较,实现了CharSequence接口,说明String是一个有序字符的集合。

2、字段

    /** The value is used for character storage. */
    // 存储字符串的字符数组
    private final char value[];

    /** Cache the hash code for the string */
    // 缓存字符串的哈希码
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    // 序列化的UID
    private static final long serialVersionUID = -6849794470754667710L;

    // 忽略大小写的比较器
    public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();

    /**
     * 很多人不知道这字段是干嘛的,其实和transient一样都是控制字段的序列化的,
     * 只不过transient修饰的字段不被序列化,而serialPersistentFields数组里
     * 的字段需要序列化,如果某个字段在serialPersistentFields数组里,且被transient
     * 修饰,则以serialPersistentFields为准,serialPersistentFields优先级高于
     * transient。
     * 这里是一个空数组,所以String类的所有字段都不会序列化
     */
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];

3、构造方法

构造方法有很多,就不写了,贴个图吧😁

4、equals方法

String类里的equals方法:

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;
    }

对比的是每个字符是否相等,还有一个equalsIgnoreCase方法,是忽略大小写对比每个字符是否相等

5、hashCode方法

String类里的hashCode方法:

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

算法不难,就是for循环里的h = 31 * h + val[i],就是这个公式:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

其中s[i]代表字符串的第i个字符,n代表字符串的长度
我们注意到这里有个31,为什么选31不选别的数呢?这里直接给出原因,具体可以参考这篇文章

  • 31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一
  • 31可以被 JVM 优化,31 * i = (i << 5) - i。因为移位运算比乘法运算更快(PS:hashMap的length为2的整数次方也是基于性能的考虑,hash(key)%length=hash(key)&(length-1),&比%快)

6、toString方法

String类里的toString方法:

public String toString() {
        return this;
    }

返回字符串本身

7、字符串常量池

为了字符串的复用,减少空间浪费,JVM会维护一个字符串常量池,常量池中的字符串不可重复,并且对长度有限制不能超过65535个字节(注意这里是字节,而不是字符)。有的同学可能有疑问,我有的类里面的
String变量有几十万个字符长,为啥不报错呢??因为那些String变量没放入常量池,有兴趣的可以参考我的这篇博客:String的长度限制

那什么样的字符串会放入常量池呢?

  • 字面量创建的字符串String str1 = "abc"或者由字符串常量拼接出的字符串String str2 = "abc"+"def"会直接放入常量池(当然前提是常量池中不存在该字符串)
  • 调用intern方法,如果常量池中没有该对象,则将该对象添加到池中,并返回池中的引用
    对于字符串常量池的详细介绍可以参考我的这篇博客:字符串常量池

8、intern方法

String类里的intern方法:

public native String intern();

可以看到这是一个本地方法。当一个String对象调用intern()方法时,如果常量池中已经有同样的字符串了,则返回该对象的引用(在堆中就返回堆中的引用,在池中就返回池中的引用),如果没有,则将该对象添加到池中,并返回池中的引用。
做个小测试:

public class Test {
    public static void main(String[] args) {
        String s1 = "liu"; // 这里将"liu"放入常量池
        String s2 = s1.intern(); // 因为s1本身就在常量池所以直接返回s1的引用
        System.out.println(s1 == s2); // true

        String s3 = new String("whut"); // 这里会在堆上创建对象
        String s4 = s3.intern(); // 因为s3在堆上,不在常量池中,所以将"whut"添加到池中,此时s4为池中"whut"的引用
        System.out.println(s3 == s4); // s3在堆上,s4在常量池中,故false

        String s5 = "whut"; // 上面已经将"whut"添加到池中了
        System.out.println(s5 == s4); // true
    }
}

结果:

9、String 真的不可变吗

what?被final修饰难道可变?
final只是限制引用不能改变,就是限制内存地址不变,但限制不了我把地址上的内容改变。(。・∀・)ノ我们知道String真正的内容放在value数组里,同样value也是被final修饰的,但依然限制不了我改
value数组里的内容。通过反射就能改,我们试一下:

public class Test {
    public static void main(String[] args) throws Exception{
        String s = "hello world";
        Field value = String.class.getDeclaredField("value");
        value.setAccessible(true);
        char[] value1 = (char[])value.get(s);
        value1[0] = 'H';
        System.out.println(s);
    }
}

结果:

可见s被改变了,不过基本不会去改String的值,如果面试官问你String是否可变,你答出这点来是不是就稳了,哈哈哈。

参考资料:
https://www.cnblogs.com/nullllun/p/8350178.html

posted @ 2020-06-28 15:44  liu_whut  阅读(243)  评论(0编辑  收藏  举报