Java String源码分析

前言

String类属于Java.lang包下的一个类,在日常开发中使用最频繁的一个类,本文主要是本人在阅读String源码过程的相关笔记。

成员变量

private final char value[];
private int hash;
复制代码

String是一个final声明的类,字符串是通过char[]来存储,并且使用hash变量存储String的hashCode。

构造函数

String提供了比较多的构造函数,如下:

image.png

主要有以下几类:

  • 根据char[]来构造。
  • 根据int[]来构造
  • 根据byte[]来构造。
  • 根据StringBuffer来构造。
  • 根据StringBuilder来构造。

其中根据int[]来构造的主要适用于码位,根据byte[]构造的主要分为unicode、ascii、byte这三种情况,鉴于本人对码位以及unicode, ascii相关知识没有深入学习,因此这几个构造函数跟相关的函数就暂时不作分析,日后希望可以补上这个短板。

根据char[]构造String

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

由于char[]是存放到堆里面的,若使用this.value=value的方式赋值,后续对this.value的相关操作会影响到value[],因此采用了Arrays.copyOf来完成深拷贝的工作。

根据byte[]来构造

    public String(byte bytes[], int offset, int length, String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(charsetName, bytes, offset, length);
    }
    
    private static void checkBounds(byte[] bytes, int offset, int length) {
        if (length < 0)
            throw new StringIndexOutOfBoundsException(length);
        if (offset < 0)
            throw new StringIndexOutOfBoundsException(offset);
        if (offset > bytes.length - length)
            throw new StringIndexOutOfBoundsException(offset + length);
    }
复制代码

在构造前,源码中利用了checkBounds来校offset+length是否越界了,也就是offset + length > bytes.length若为真,那么就抛出异常。

校验都通过后,利用了String.decode来讲bytes转换为String。

根据StringBuffer来构造

    public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }
复制代码

由于StringBuffer是线程安全的,为了避免在buffer.getValue()的时候,buffer还在操作字符串,就需要添加synchronized来对buffer进行同步处理。StringBuilder的构造与StringBuffer差不多,只是缺少了syncronized。

charAt源码分析

    public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }
复制代码

通过源码,可以看出charAt实际是这些操作value数组的,通过入参index就可以获取对应的值。

getChars源码分析

    public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }
复制代码

以上的函数主要对入参做相关的合法性校验,校验通过后,通过System.arraycopy来对value[]进行复制。

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;
    }
复制代码

大部分类的equlas的通常都先通过this == anObject方法来判断是否相等,如果条件达到,直接返回true。如果不一致,那么就判断内容是否相等。

String的equals判断内容是否相等前,会判断字符串的长度是否相等,如果不相等,那么就通过循环逐位判断,若存在不相等的字符,就返回false。

nonSyncContentEquals源码分析

    private boolean nonSyncContentEquals(AbstractStringBuilder sb) {
        char v1[] = value;
        char v2[] = sb.getValue();
        int n = v1.length;
        if (n != sb.length()) {
            return false;
        }
        for (int i = 0; i < n; i++) {
            if (v1[i] != v2[i]) {
                return false;
            }
        }
        return true;
    }
复制代码

主要用于String实例与StringBuffer以及StringBuilder实例判断是否相等,主要原理还是先比较字符长度是否一致,最后再逐位比较字符是否相等。

contentEquals源码分析

    public boolean contentEquals(CharSequence cs) {
        // Argument is a StringBuffer, StringBuilder
        if (cs instanceof AbstractStringBuilder) {
            if (cs instanceof StringBuffer) {
                synchronized(cs) {
                   return nonSyncContentEquals((AbstractStringBuilder)cs);
                }
            } else {
                return nonSyncContentEquals((AbstractStringBuilder)cs);
            }
        }
        // Argument is a String
        if (cs instanceof String) {
            return equals(cs);
        }
        // Argument is a generic CharSequence
        char v1[] = value;
        int n = v1.length;
        if (n != cs.length()) {
            return false;
        }
        for (int i = 0; i < n; i++) {
            if (v1[i] != cs.charAt(i)) {
                return false;
            }
        }
        return true;
    }
复制代码

继承CharSequence类的子类有StringBuilder、StringBuffer以及String,因此针对StringBuilder以及StringBuffer需要进行另一种方式的比较。

通过cs instanceof AbstractStringBuilder,就可以知道cs为StringBuffer或者StringBuilder了,再通过cs instanceof StringBuffer来判断是否需要加入同步锁synchronized。

通过cs instanceof String来判断cs是否为String类,若是,那么直接调用String.equals来比较即可。

若都不属于以上这几种情况,则就需要根据CharSequence来判断字符是否相等了,采用的原理也是逐位判断。

equalsIgnoreCase源码分析

    public boolean equalsIgnoreCase(String anotherString) {
        return (this == anotherString) ? true
                : (anotherString != null)
                && (anotherString.value.length == value.length)
                && regionMatches(true, 0, anotherString, 0, value.length);
    }
复制代码

忽略字符串的大小写后,判断字符串是否相等。在调用regionMatches方法比较前,需要先符合anotherString与this、this.value的字符串长度一致。regionMatches方法在以下分析。

regionMatches方法

String regionMatches.png

关于以上统一转换大写字符比较一次后,若不成功,再统一转换成小写字符进行一次,通过注释可知,主要度其他字母不能够转换大写的一个补偿处理。

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计算方式为:s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1],其中s[i]是字符串的第i个字符, n是字符串的长度, ^表示幂。 (空字符串的哈希值为零。)

从源码中可以看出hashCode的计算使用了31作为乘子,主要考虑有以下原因:

  • 31是一个不大不小的质数,是作为hashCode乘子的优选质数之一。另外一些相近的质数,比如37、41、43等,也都是不错的选择。那么为什么选中了31呢?请看第二个原因。
  • 31可以被JVM优化,31 ∗ i =(i<<5)- i。

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;
    }
复制代码

该方法源码很好理解,即按字母顺序比较两个字符串,是基于字符串中每个字符的Unicode值。当两个字符串某个位置的字符不同时,返回的是这一位置的字符Unicode值之差,当两个字符串都相同时,则返回两个字符串长度之差。

参考

  • java修炼指南:高频源码解析
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享