前言

最近打算开始来读一下JDK的局部源码,这次先从咱们平时用的最多的String类(JDK1.8)开始,本文次要会对以下几个办法的源码进行剖析:

  • equals
  • hashCode
  • equalsIgnoreCase
  • indexOf
  • startsWith
  • concat
  • substring
  • split
  • trim
  • compareTo

如果有不对的中央请多多指教,那么开始进入注释。

源码分析

equals

equals() 办法用于判断 Number 对象与办法的参数进是否相等

String类重写了父类Object的equals办法,来看看源码实现:

  1. 首先会判断两个对象是否指向同一个地址,如果是的话则是同一个对象,间接返回true
  2. 接着会应用instanceof判断指标对象是否是String类型或其子类的实例,如果不是的话则返回false
  3. 接着会比拟两个String对象的char数组长度是否统一,如果不统一则返回false
  4. 最初迭代顺次比拟两个char数组是否相等

hashCode

hashCode() 办法用于返回字符串的哈希码

Hash算法就是一种将任意长度的消息压缩到某一固定长度的音讯摘要的函数。在Java中,所有的对象都有一个int hashCode()办法,用于返回hash码。

依据官网文档的定义:Object.hashCode() 函数用于这个函数用于将一个对象转换为其十六进制的地址。依据定义,如果2个对象雷同,则其hash码也应该雷同。如果重写了 equals() 办法,则原 hashCode() 办法也一并生效,所以也必须重写 hashCode() 办法。

依照下面源码举例说明:

String msg = "abcd"; System.out.println(msg.hashCode());

此时value = {'a','b','c','d'}  因而for循环会执行4次

第一次:h = 31*0 + a = 97 
第二次:h = 31*97 + b = 3105 
第三次:h = 31*3105 + c = 96354 
第四次:h = 31*96354 + d = 2987074 

由以上代码计算能够算出 msg 的hashcode = 2987074

在源码的hashcode的正文中还提供了一个多项式计算形式:

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

另外,咱们能够看到,计算中应用了31这个质数作为权进行计算。能够尽可能保障数据分布更扩散

在《Effective Java》中有提及:

之所以抉择31,是因为它是一个奇素数。如果乘数是偶数,并且乘法溢出的话,信息就会失落,因为与2相乘等价于移位运算。应用素数的益处并不显著,然而习惯上都应用素数来计算散列后果。31有个很好的个性。即用移位和减法来代替乘法,能够失去更好的性能:31 * i == (i << 5) - i。古代的VM能够主动实现这种优化。
/** Cache the hash code for the string */private int hash; // Default to 0

而且如下面所示,当计算完之后会用一个变量hash把哈希值保存起来,下一次再获取的时候就不必换从新计算了,正是因为String的不可变性保障了hash值的惟一。

equalsIgnoreCase

equalsIgnoreCase() 办法用于将字符串与指定的对象比拟,不思考大小写

接下来来看看源码实现:

来看看外围办法

置信看了上图的介绍就能看懂了,这里就不多说了。

indexOf

查找指定字符或字符串在字符串中第一次呈现中央的索引,未找到的状况返回 -1
String str = "wugui";System.out.println(str.indexOf("g"));
输入后果:2
public int indexOf(String str) {   return indexOf(str, 0);}public int indexOf(String str, int fromIndex) {   return indexOf(value, 0, value.length,str.value, 0, str.value.length, fromIndex);}

接下来是咱们的外围办法,先看下各个参数的介绍

/* * @param   source       被搜寻的字符 * @param   sourceOffset 原字符串偏移量 * @param   sourceCount  原字符串大小 * @param   target       要搜寻的字符 * @param   targetOffset 指标字符串偏移量 * @param   targetCount  指标字符串大小 * @param   fromIndex    开始搜寻的地位*/static int indexOf(char[] source, int sourceOffset, int sourceCount,        char[] target, int targetOffset, int targetCount,        int fromIndex) {   ......}

上面是代码的逻辑步骤

indexOf的源码外面我认为边界条件是写的比拟好的

咱们这里假如

String str = "wugui";str.indexOf("ug");

在上图第2步,计算出max作为上面循环的边界条件

//找到第一个匹配的字符索引if (source[i] != first) {   while (++i <= max && source[i] != first);}

咱们计算出 max=3,也就是说咱们在应用迭代搜寻第一个字符的时候只须要遍历到索引为3的地位,就能够了,因为索引第4位也就是最初一位 'i',就是匹配到了第一个字符也是无意义的,因为咱们要搜寻的指标自字符是2位字符,同第5步计算出end作为边界条件也是同样的情理。

有了indexOf办法之后,那有些办法就能够借用它来实现了,比方contains办法,源码如下:

public boolean contains(CharSequence s) {   return indexOf(s.toString()) > -1;}

只须要调用依据indexOf的返回值来判断是否蕴含指标字符串就能够了。

startsWith

startsWith() 办法用于查看字符串是否是以指定子字符串结尾,如果是则返回 True,否则返回 False
String str = "wugui";System.out.println(str.startsWith("wu"));
输入后果:true
public boolean startsWith(String prefix) {    return startsWith(prefix, 0);}public boolean startsWith(String prefix, int toffset) {    ......}

既然有了startsWith办法,那么endsWith就很容易实现了,如下:

只有批改一下参数,设置偏移量就能够了。

concat

用于将指定的字符串参数连贯到字符串上
String str1 = "wu";String str2 = "gui";System.out.println(str1.concat(str2));
输入后果:wugui

能够看到是应用了Arrays.copyOf办法来生成新数组

char buf[] = Arrays.copyOf(value, len + otherLen);

咱们来看看其实现:

能够看到次要应用system.arraycopy办法,点进去看一下实现:

如果看不到的话咱们这里举个例子:

比方 :咱们有一个数组数据

byte[] srcBytes =  new byte[]{2,4,0,0,0,0,0,10,15,50};//原数组byte[] destBytes = new byte[5]; //指标数组

咱们应用System.arraycopy进行复制

System.arrayCopy(srcBytes,0,destBytes ,0,5)

下面这段代码就是 : 创立一个一维空数组,数组的总长度为 12位,而后将srcBytes源数组中 从0位 到 第5位之间的数值 copy 到 destBytes指标数组中,在指标数组的第0位开始搁置,
那么这行代码的运行成果应该是 2,4,0,0,0,

调用完Arrays.copy返回新数组办法后,会调用str.getChars(buf, len)来拼接字符串,咱们看下其实现:

能够看到其实也是调用了System.arraycopy来实现,这里不再细说。

最初一步就是把新数组赋值给value

return new String(buf, true);

substring

提取字符串中介于两个指定下标之间的字符
String str = "wugui";System.out.println(str.substring(1, 3));//包含索引1不包含索引3
输入后果:ug

来看看 new String(value, beginIndex, subLen) 的实现

看看Arrays.copyOfRange是如何实现的:

能够看到其实还是应用的System.arraycopy来实现,下面曾经介绍过了,这里不再细说。

split

依据匹配给定的正则表达式来拆分字符串

先来看看用法:

public String[] split(String regex, int limit)

第一个参数regex示意正则表达式,第二个参数limit是宰割的子字符串个数

String str = "a:b:c:d";String[] split = str.split(":");

当没有传limit参数默认调用的是split(String regex, 0)

下面的输入为:[a, b, c, d]

如果把limit参数换成2那么输入后果变成:[a, b:c:d],能够看出limit意味着宰割后的子字符串个数。

看看整个源码:

 public String[] split(String regex, int limit) {        /* fastpath if the regex is a         (1)one-char String and this character is not one of the            RegEx's meta characters ".$|()[{^?*+\\", or         (2)two-char String and the first char is the backslash and            the second is not the ascii digit or ascii letter.         */        char ch = 0;        //如果regex只有一位,且不为列出的特殊字符;         //如果regex有两位,第一位为转义字符且第二位不是数字或字母         //第三个是和编码无关,就是不属于utf-16之间的字符        if (((regex.value.length == 1 &&             ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||             (regex.length() == 2 &&              regex.charAt(0) == '\\' &&              (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&              ((ch-'a')|('z'-ch)) < 0 &&              ((ch-'A')|('Z'-ch)) < 0)) &&            (ch < Character.MIN_HIGH_SURROGATE ||             ch > Character.MAX_LOW_SURROGATE))        {            int off = 0;            int next = 0;            boolean limited = limit > 0;            ArrayList<String> list = new ArrayList<>();            while ((next = indexOf(ch, off)) != -1) {                if (!limited || list.size() < limit - 1) {                    list.add(substring(off, next));                    off = next + 1;                } else {    // last one                    //assert (list.size() == limit - 1);                    list.add(substring(off, value.length));                    off = value.length;                    break;                }            }            // If no match was found, return this            if (off == 0)                return new String[]{this};            // Add remaining segment            if (!limited || list.size() < limit)                list.add(substring(off, value.length));            // Construct result            int resultSize = list.size();            if (limit == 0) {                while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {                    resultSize--;                }            }            String[] result = new String[resultSize];            return list.subList(0, resultSize).toArray(result);        }        return Pattern.compile(regex).split(this, limit);    }

接下来咱们一步步来剖析:

能够看到有三个条件:

  1. 如果regex只有一位,且不为列出的特殊字符
  2. 如果regex有两位,第一位为转义字符且第二位不是数字或字母
  3. 第三个是和编码无关,就是不属于utf-16之间的字符

只有满足下面三个条件能力进入下一步:

第一次宰割时,应用offnextoff指向每次宰割的起始地位,next指向分隔符的下标,实现一次宰割后更新off的值,当list的大小等于limit-1时,间接增加剩下子字符串,具体看下源码:

最初就是对子字符串进行解决:

集体感觉这部分源码还是比拟难的,有趣味的同学能够再去钻研一下。

trim

删除字符串的头尾空白符
String str = "  wugui         ";System.out.println(str.trim());
输入:wugui

这部分还是比较简单的,这里不再细说。

compareTo

比拟两个字符
String a = "a";String b = "b";System.out.println(a.compareTo(b));
输入:-1

看看源码:

总结

无关String的源码临时剖析到这里,其它的源码感兴趣的小伙伴能够按本人去钻研一下,接下来可能会得写几篇文章来介绍一下Java中的包装类,敬请期待!