String源码解析

一、前言

    众所周知字符串String是不可变的,当你改变其字符串内容的时候,他的底层是重新创建一个新的字符串,并且让栈中的对象引用指向新的字符串的地址的,那到底这是怎么实现的呢?接下来我们一起去看看String字符串的底层源码是如何实现的。

二、String内部的变量有哪些?

    在看String内部变量之前我们先看看String继承了哪些接口,其源码如下所示:

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

    String内的变量有如下所示:

/**用来存储字符串的,字符串会变成字符存在一个不可变的char数组里*/
private final char value[];
/** 其默认的hash值为0 */
private int hash;
/**序列化的值,用来反序列化时进行验证的,来判断其有没有修改过 */
private static final long serialVersionUID = -6849794470754667710L;
/**类字符串在序列化流协议中使用特殊大小写。 */
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
/**这个是String的一个内部类CaseInsensitiveComparator()的实例,用来调用其内部类的compare()方法 */
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();

    刚刚也说到了String里面有一个内部类,其继承了Comparator和 java.io.Serializable接口,实现了compare方法,其源码如下所示:

public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();

    //内部类CaseInsensitiveComparator实现Comparator接口,重写其compare方法
    private static class CaseInsensitiveComparator
            implements Comparator<String>, java.io.Serializable {
        // use serialVersionUID from JDK 1.2.2 for interoperability
        private static final long serialVersionUID = 8575799808933029326L;
		/**
         * compare也是将参数s1和s2先转化为char数组,如何拿取指定下标的char来进行比较
         * 不过它需要对其大小写都进行一次比较
         * @param s1
         * @param s2
         * @return
         */
    public int compare(String s1, String s2) {
        int n1 = s1.length();
        int n2 = s2.length();
        int min = Math.min(n1, n2);
        for (int i = 0; i < min; i++) {
            char c1 = s1.charAt(i);
            char c2 = s2.charAt(i);
            if (c1 != c2) {
                //转换为大写
                c1 = Character.toUpperCase(c1);
                c2 = Character.toUpperCase(c2);
                if (c1 != c2) {
                    //转化为小写
                    c1 = Character.toLowerCase(c1);
                    c2 = Character.toLowerCase(c2);
                    if (c1 != c2) {
                        // No overflow because of numeric promotion
                        return c1 - c2;
                    }
                }
            }
        }
        return n1 - n2;
    }

    /** 替换反序列化的对象. */
    private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}

三、String内的一些常用方法的源码实现

1.compareToIgnoreCase(String str)方法,从下面可知其实现原理就是调用String的内部类所实现的compare方法,compare回对其char数组内的char进行大小写转化并且判断。

/**
 * 判断两个字符串的内容是否一样,不区分大小写
 * @param str
 * @return
 */
public int compareToIgnoreCase(String str) {
    return CASE_INSENSITIVE_ORDER.compare(this, str);
}

2.startsWith(String prefix)方法,其作用是看是不是此字符串是以前缀prefix开头的,源码如下:

//其作用是看是不是此字符串是以前缀prefix开头的
public boolean startsWith(String prefix) {
    return startsWith(prefix, 0);
}

    可以看到其内部实现是通过**startsWith(String prefix, int toffset)**方法实现的,那次方法又是怎么实现的呢?源码如下所示:

//从字符串中,将指定的prefix字符串和toffset以后的字符串比较是否以prefix开头为前缀
public boolean startsWith(String prefix, int toffset) {
    char ta[] = value;
    int to = toffset;
    char pa[] = prefix.value;
    int po = 0;
    int pc = prefix.value.length;
    // 对offset的范围进行判断
    if ((toffset < 0) || (toffset > value.length - pc)) {
        return false;
    }
    while (--pc >= 0) {
        if (ta[to++] != pa[po++]) {
            return false;
        }
    }
    return true;
}

3.endsWith(String suffix)方法其作用就是看字符串是不是以suffix结尾,其底层实现如下所示,可以看到其还是调用了**startsWith(String prefix, int toffset)**方法来实现的。

public boolean endsWith(String suffix) {
    return startsWith(suffix, value.length - suffix.value.length);
}

4.hashCode()方法,其作用是获取当前字符串的一个hash值,源码如下所示:

public int hashCode() {
    //一开始是0
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value
        //用31*h加上其char上的Unicode编码值
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

5.indexOf(int ch)方法,获取字符串为ch的Unicode编码的字符的下标,其源码如下所示:

public int indexOf(int ch) {
    return indexOf(ch, 0);
}

    可以看出其内部实现是通过**indexOf(int ch, int fromIndex)**方法来实现的,那么这个方法的内部实现又是怎样呢?

 public int indexOf(int ch, int fromIndex) {
    final int max = value.length;
    //对fromIndex进行范围的验证,看看有没有符合规范
    if (fromIndex < 0) {
        fromIndex = 0;
    } else if (fromIndex >= max) {
        // fromIndex可能接近-1>>>1
        return -1;
    }
    //MIN_SUPPLEMENTARY_CODE_POINT = 0x010000,十进制值为16*16*16*16
    if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
        // handle most cases here (ch is a BMP code point or a
        // negative value (invalid code point))
        //这里处理大多数情况(ch是BMP代码点或一个负值(无效代码点))
        final char[] value = this.value;
        for (int i = fromIndex; i < max; i++) 
            if (value[i] == ch) {
                return i;
            }
        }
        return -1;
    } else {
        return indexOfSupplementary(ch, fromIndex);
    }
}

6.lastIndexOf(int ch)标识从后往前数,为ch的Unicode编码的字符的下标,其源码如下所示:

public int lastIndexOf(int ch) {
    return lastIndexOf(ch, value.length - 1);
}

    可以看出其内部实现是通过**lastIndexOf(int ch, int fromIndex)**方法来实现的,那么这个方法的内部实现又是怎样呢?

public int lastIndexOf(int ch, int fromIndex) {
    if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
        // handle most cases here (ch is a BMP code point or a
        // negative value (invalid code point))
        final char[] value = this.value;
        int i = Math.min(fromIndex, value.length - 1);
        for (; i >= 0; i--) {
            if (value[i] == ch) {
                return i;
            }
        }
        return -1;
    } else {
        return lastIndexOfSupplementary(ch, fromIndex);
    }
}

7.substring(int beginIndex)是获取从beginIndex下标开始到最后一个位置的字符串,如何返回一个新的字符串,其源码如下所示:

public String substring(int beginIndex) {
    //判断下标是否符合规范,有没有越界
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    //从begin到字符串总长度的范围
    int subLen = value.length - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    //判断是不是从0开始,如果是就直接返回原字符串
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

8.concat(String str)则是将指定的字符串连接到此字符串的末尾,其源码如下所示:

//将指定的字符串连接到此字符串的末尾。
public String concat(String str) {
    //获取要添加到当前字符串末尾的字符串的长度
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    //获取当前字符串长度
    int len = value.length;
    //将当前字符串长度和添加的字符串长度根据Arrays.copyOf来获取一个新的char数组
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    //返回一个新的字符串
    return new String(buf, true); 
 }

注意:从**return new String(buf, true);**可以看出,对String操作获取新的字符串其实是重新返回一个新的String对象,而不是在原字符串上修改的。

9.contains(CharSequence s)方法是判断字符串是否包含char值的指定序列,其源码如下所示:

public boolean contains(CharSequence s) {
    //判断其所属的下标有没有大于-1,主要还是用到了indexOf方法
    return indexOf(s.toString()) > -1;
}

10.toCharArray()方法,返回字符串对呀的char数组,其底层实现如下所示:

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

11.valueOf(char data[])返回char数组的内容组成的字符串

public static String valueOf(char data[]) {
    //根据其构造方法来实现的
    return new String(data);
}

12.String还有一个本地方法intern(),用来返回字符串对象的规范表示形式,如下所示:

//返回字符串对象的规范表示形式。
public native String intern();

四、总结

    String的源码一共三千多行,有些也挺难理解的,我只是把自己看String源码的一些感想或者说理解写出来,当一个笔记吧,等差不多忘记了也可以从这篇博客中回忆一下。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值