String 源码分析

Java 源码阅读 - String

String 类型看起来简单,实际上背后的复杂性基本可以涵盖了整个 Java 设计,涉及到设计模式(不可变对象)、缓存(String Pool 的理念)、JVM(String Pool 在 JVM 的模块)等。对 String 了解的多少可以直接体现一个人 Java 乃至对程序设计的水平。

以前真的是管中窥豹。

常见面试题

Q

  • 基础题目

    • String 可以被继承吗?(语法)
    • String a = "str", b = "str", a == b 吗?(JVM 中的 String Pool)
    • String a = new String("str"), b = new String("str"),a == b 吗?(引用)
    • String、 StringBuffer、 StringBuilder 有什么区别?(类库)
  • 比较高级的题目

    • String 设计成不可变类有什么好处?(设计模式)
    • "s" + "tr" == "str" 吗?(JVM 编译期优化)
    • JDK 8 中,String str1 = new StringBuilder("ja").append("va").toString(),str1 == str1.intern() 返回什么结果?(JVM)

A

  • 基础题目

    • 不可以,final
    • 等于,都是从 String Pool 中取,引用相等
    • 不等于,没有从 String Pool 中取
    • String 对比 StringBuffer、StringBuilder:String 不可变,StringBuffer、 StringBuilder 可变;StringBuffer 对比 StringBuilder:StringBuffer 线程安全(synchronize)导致效率低,StringBuilder 线程不安全效率高
  • 比较高级的题目

    • 可以缓存相关字符串(在 String Pool,之后会解释它是什么),节约空间
    • 等于,编译器优化,直接将 "s" + "tr" 优化成 "str",之后从 String Pool 中取,引用相等
    • false,一个是从 String Pool 中取的引用,另一个直接 new

概念:String Pool

英文版:

Thanks to the immutability of Strings in Java, the JVM can optimize the amount of memory allocated for them by storing only one copy of each literal String in the pool. This process is called interning.

When we create a String variable and assign a value to it, the JVM searches the pool for a String of equal value.

If found, the Java compiler will simply return a reference to its memory address, without allocating additional memory.

If not found, it’ll be added to the pool (interned) and its reference will be returned.

简要概况:

String 对象是不可变的,为 String Pool 提供了条件。Java 里可以通过 String.intern() 方法获取在 String Pool 中的对象。
JVM 会创建 String Pool。如果字符串存在于 Pool 中,取 Pool 中的值;如果字符串不存在于 Pool 中,创建然后返回引用。

不可变对象

  • 类被 final 修饰
  • 变量被 final 修饰,在构造器初始化
  • 方法返回的对象为克隆之后新的对象

JVM 对于 String Pool 的处理

  • 在 JDK 6 以及之前,String Pool 存在于 Method Area(方法区,主要保存类的信息,又称永久代),占用 JVM 内存;String Pool 保存字符串是通过先复制再返回复制过后的引用
  • 在 JDK 7 的时候,String Pool 转移到 Heap(实例数据存放的地方),占用 JVM 内存;String Pool 直接记录了第一次字符串出现的引用,以后就返回该引用
  • 在 JDK 8 的时候,String Pool 转移到 Metaspace(相当于 Method Area 另一种实现),占用系统内存;String Pool 直接记录了第一次字符串出现的引用,以后就返回该引用

源码阅读

构造器

最基础的构造器一般是字符数组,通过 clone 的方式,保证了数组修改,字符串内容不会变。

    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

下面的构造器简单的复制了之前的字符数组。由于之前的字符数组是克隆的,类本身不会去修改字符数组的内容,所以这里直接复制引用就可以保证不变性。这里并没有走 String Pool,所以类似 "abc" == new String("abc") 的引用地址不同。

    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

equals 方法首先比较了引用,之后比较了内容。如果都是从 String Pool 里的取的引用,那地址肯定相同;如果不是,则可能不同,需要比较具体内容。

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

hashcode 方法采取了常用 hashcode 算法:若为数组,hash = preHash * 31 + value[i]。注意 hashcode 是会相同的,"Aa"、"BB" 就相同,所以在 HashMap 中,是先比较 hashcode,如果发现有相同的 hashcode 的对象,再用 equals 进行比较。

    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;
    }
原文地址:https://www.cnblogs.com/Piers/p/9844293.html