【JAVA】String源码浅谈

版权声明:转载请注明 https://blog.csdn.net/qq_33591903/article/details/84778059

                                           String源码浅谈

String这个类可以说是我们使用得最为频繁的类之一了,前几次去面试,都被问到String的底层源码,回答得都不是很好,今天就来谈谈一下String的源码。

一、String类

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

String类被是被final修饰的,表明该类不可被继承,有关于关键词final的作用,请移步我的另外一篇文章【JAVA】关键词final的作用。值得一提的是,StringBuilder,StringBuffer类都是被final修饰的。


二、String类的属性

  /** 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 */
    private static final long serialVersionUID = -6849794470754667710L;

(1)char value[]

String底层的存储结构是一个字符类型的数组,同样也是被final修饰,因此一旦这个字符数组被创建后,value变量不可再指向其他数组,但是可以改变value数组中某一个元素的值。

(2)int hash

hash用来保存某一个String实例自己的哈希值,可以说是哈希值的一个缓存,因此String特别适合放入HashMap中,作为key来使用。每次插入一个键值対时,不需要重新计算key的哈希值,直接取出key的缓存hash值即可,在一定程度上,加快了HashMap的效率。

(3)long serialVersionUID

用于保证版本一致性。由于String实现了Serializable接口,因此需要拥有一个序列化的ID。序列化时,将此ID与对象一并写入到文件中,反序列化时,检测该类中的ID与文件中的ID是否一致,一致的话,说明版本一致,序列化成功。


三、String类的构造函数

(1)无参构造函数,创建一个空字符串,即"",用得地方不多。

    public String() {
        this.value = new char[0];
    }

(2)接收一个String实例的构造函数

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

(3)接收一个字符数组,利用Arrays.copyOf()方法进行拷贝

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

进入到Arrays.copyOf()方法中,发现调用的是System.arraycopy()方法,Arrays.copyOf()方法如下

    public static char[] copyOf(char[] original, int newLength) {
        char[] copy = new char[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

而System.arraycopy()方法是一个本地方法,由其他语言实现。

从以上源码,可以看得出,这个构造方法没有直接使用传入的字符数组的引用,而是使用该数组的一个拷贝,保证了String类的不可变性。我们无法通过在外部改变此数组中某些元素的值,来改变构造后的String的值。

同样在toCharArray()方法中,也是返回一个基于字符数组的拷贝,并没有直接直接返回value数组。

    public char[] toCharArray() {
        // Cannot use Arrays.copyOf because of class initialization order issues
        char result[] = new char[value.length];
        System.arraycopy(value, 0, result, 0, value.length);
        return result;
    }

另外,有关于System.arraycopy()、Arrays.copyOf()之间的效率问题,可以参考我的另外一篇文章【JAVA】数组复制效率的比较


(4)接收一个字符数组,从offset位置开始复制,一共选取count位

    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

其他的构造函数的原理大同小异,这里就不再说明了。


四、String类的其他方法

(1)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;
    }

String类重写了equals()方法,判断两个String实例代表的字符串是否相同。

判断规则:

如果两个Stirng实例压根就是一个对象,即它们的内存地址相同,则直接返回true。

之后,对anObject进行类型判断,类型为String后,继续判断,否则直接返回false。

再对两者的长度进行判断,如果相等,继续判断,否则返回false。

两者长度相等后,再从前往后依次比较两者字符数组中的元素是否相等,全相等的后,返回true。

equals()方法一上来没有直接比较两个字符串的字符数组元素,在比较超长的字符串时,节省了大量的时间。


(2)compareTo()方法

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

比较两个字符串,可以用来排序。String类中还有一个内部类CaseInsensitiveComparator,其中也有一个compare()方法,与compareTo()方不同的是,compare()进行比较时,会忽略两个字符串的大小写。


(3)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;
    }

String类同样也重写了hashCode()方法,用于计算String实例的哈希值。

哈希值相同的两个字符串不一定相同,相同的字符串的哈希值一定相同。

至此,String类重写了equals(),也重写了hashCode()方法。关于两个方法之间的关系,可以参考我的另外一篇文章【JAVA】为什么重写equals(),就必须要重写hashCode()?


(4)intern()方法

    public native String intern();

可以看得出,它是一个本地方法。

当调用此方法时,会首先在方法区中的常量池中使用equals()寻找是否存在此字符串,如果存在,直接返回此字符串的引用。如果不存在时,会首先将此字符串添加到常量池中,再返回该字符串的引用。

下面通过一个例子来说明

package day1203;

//String类的源码
public class StringTest {
    public static void main(String[] args) {
        String a = "abc";//这个abc在常量池中创建
        String b = new String("abc");//这个abc在堆上创建
        String c = b.intern();//在常量池中寻找是否存在abc,若存在的话,直接返回它的引用。
        System.out.println(a == b);//返回false
        System.out.println(a == c);//返回true
    }
}

这里涉及字符串的创建与存储方式,可以参考我的另外一篇文章【JAVA】字符串的创建与存储机制


五、总结

String类中还有很多有趣的操作,比如字符串的截取、匹配、替换、大小写转换、分割等操作,这里都没有涉及。这些操作确实都是经常用到的,相信大家也能够理解他们的用法,这里就不再赘述了。

如果还有一些疑问,也欢迎大家在下方评论,我一定会及时回复。

猜你喜欢

转载自blog.csdn.net/qq_33591903/article/details/84778059