JDK1.8源码阅读——String

    人不丑话也不多,直接开始啦~

一、结构预览

    由于String包含的方法实在太多,因此这里就不列出它的结构树了。给大家看一下本篇博客的主要内容的目录吧。

1. String类属性

2. String成员变量

3. String构造器

4. String是如何重写equals()的

5. String常用的工具方法

二、正文

1. String类属性

    首先看一下String类的定义:

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

    可以看到String继承了序列化接口、Comparable接口以及CharSequence 接口

    Serializable:序列化接口,至于为什么要实现这个接口,个人觉得可能与数据的存储与传输有关。由于String的应用范围实在是太广了,例如我们常用的数据库连接信息、http、socket端口和IP等都是字符串,又比如redis、memcache等缓存服务也使用了String作为缓存类型,而这些应用场景,都与序列化密不可分。以上属于个人理解,不当之处,可以指出。

    Comparable:用于比较字符串的大小,具体的实现方法在下文。

    CharSequence:有序的字符集,CharSequence就是字符序列,String, StringBuilder和StringBuffer本质上都是通过字符数组实现的,CharSequence 是 char 值的一个可读序列。此接口对许多不同种类的 char 序列提供统一的只读访问。

2. String成员变量

private final char value[];

private int hash;

private static final long serialVersionUID = -6849794470754667710L;

    所以,一个字符串是由一个序列化标识、散列值、储存字符串的char数组 组成的。

    注意:一个空字符串的散列值为0,原因在下文介绍

3. String构造器

    

    可以看到,构造一个String的方法是在是太多了,这里就不做介绍了,主要说一下下面几点:

    3.1  String str = "" 与 new String()

    这里要提到java中的一个概念:常量池。那么什么是(String)常量池呢?

    由于Java中String的应用是在是太广泛了,可以说是随处可见。为了避免资源的浪费,重复的去创建相同的字符串,所以Java运行时会维护一个String Pool(String池), 也叫“字符串缓冲区”。String池用来存放运行时中产生的各种字符串,并且池中的字符串的内容不重复。

    使用String str = ""去构造一个字符串时,会去常量池中去查找池中是否存在这样一个字符串,如果存在,则直接引用此字符串,如果不存在,再去创建一个新的字符串并维护到池中。

    new String()则不进行判断,直接创建一个新的字符串。示例如下:

String str1 = "helloworld";
String str2 = new String("helloworld");
String str3 = "hello"+"world";
System.out.println(str1 == str2);	//运行结果为:false
System.out.println(str2 == str3);	//运行结果为:false
System.out.println(str1 == str3);	//运行结果为:true

    3.2  intern

public native String intern();

    这里顺带提一下intern这个方法,它的作用是返回当前String的内存地址或字符串。当调用intern方法时,如果池中已经包含一个与该String确定的字符串相同equals(Object)的字符串,则返回该字符串。否则,将此String对象添加到池中,并返回此对象的引用。

String str1 = "helloworld";
String str2 = str1.intern();
System.out.println(str2);		//打印helloworld

4 equals与hashCode

    在上篇博客中,我们知道如果重写equals方法,那么必须重写hashCode方法。String对Object的equals进行了重写,使其判断的是字符串内容是否相等,而不是引用地址。所以String的hashCode也是对字符串的内容进行hash

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

    重写后的equals,首先对其引用地址进行判断,如果不同,再判断其类型。在满足类型相同的条件下,获取其存储字符串的变量value,首先对长度进行判断,这样意味着“helloworld”与“hello world”是不相等的。如果长度相同,再通过循环去比较value数组的每一个元素是否相同,只要存在一个元素不同,返回false。

    上文说过,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;
    }

    注意这里的判断是如果hash为0或者字符串长度>0,才会去计算hash值,否则返回默认的hash值0(int的默认值为0)。

    关于这里为何使用31,而不是定义其他的常量,网上大致的解释有两点,这里仅做参考:

    a.  31本身就是几个优质的质因数之一,使用31能够减少hash碰撞

    b.  31=2<<5-1,效率故而较高

5.  String常用的方法

5.1  isEmpty

public boolean isEmpty() {
    return value.length == 0;
}

    用于判断字符串是否为空,不包括null的情形,因此使用这个方法前需要去考虑它是否为null。

5.2  charAt

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

    用于获取指定位置的字符,使用该方法需要对字符串长度进行判断,否则有可能出现下标越界的异常。

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

    比较两个字符串的大小,本质上是比较阿斯克码的大小,通常用于比较排序。需要注意的是,通过循环的内容,我们可以发现排序的优先级是从前往后的,当靠前的字符不相等时,就会直接返回,不再继续比较。

5.4  indexOf

public int indexOf(int ch) {
   return indexOf(ch, 0);
}
    public int indexOf(int ch, int fromIndex) {
        final int max = value.length;
        if (fromIndex < 0) {
            fromIndex = 0;
        } else if (fromIndex >= max) {
            // Note: fromIndex might be near -1>>>1.
            return -1;
        }

        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;
            for (int i = fromIndex; i < max; i++) {
                if (value[i] == ch) {
                    return i;
                }
            }
            return -1;
        } else {
            return indexOfSupplementary(ch, fromIndex);
        }
    }
    private int indexOfSupplementary(int ch, int fromIndex) {
        if (Character.isValidCodePoint(ch)) {
            final char[] value = this.value;
            final char hi = Character.highSurrogate(ch);
            final char lo = Character.lowSurrogate(ch);
            final int max = value.length - 1;
            for (int i = fromIndex; i < max; i++) {
                if (value[i] == hi && value[i + 1] == lo) {
                    return i;
                }
            }
        }
        return -1;
    }

    indexOf通常用于判断某个字符是否存在于当前字符串并获取其下标,indexOf支持从0开始查找,也支持从指定的下标位置开始查找,如果不存在,返回-1。

5.5  substring

    substring有多种参数的实现方法,这里只介绍substring(int beginIndex),毕竟它们实现的方法基本相同。其源码如下:

    public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

    相对于indexOf,substring没有处理beginIndex<0的情况,也没有处理beginIndex超出字符串长度的情况,都是直接抛出了下标越界的异常,因此我们在使用时,一定要注意避免这种情况。

     substring的实现方法为:String(char value[], int offset, int count),该方法是源码如下:

    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
                this.value = "".value;
                return;
            }
        }
        // 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);
    }

    在这个方法里,主要关注的是Arrays提供的方法:public static char[] copyOfRange(char[] original, int from, int to),通过这里我们可以知道,substring的实现是通过数组的拷贝实现的,而数组的拷贝底层是通过System.arraycopy这个non-java方法实现的。

5.6  split

    我们常用的split(String regex)方法其底层是通过split(String regex, int 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;
        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);
    }

5.7  trim

    去除字符串头尾的空格,中间部分的空格是不做处理的。源码如下:

    public String trim() {
        int len = value.length;
        int st = 0;
        char[] val = value;    /* avoid getfield opcode */

        while ((st < len) && (val[st] <= ' ')) {
            st++;
        }
        while ((st < len) && (val[len - 1] <= ' ')) {
            len--;
        }
        return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
    }

   其原理就是通过阿斯克码值得判断去截取字符串。

5.8  valueOf

    获取当前对象的String,大致包括以下方法:

    valueOf(Object obj)

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}

        添加了null判断,最终调用Object的toString方法。

    valueOf(boolean b) 

public static String valueOf(boolean b) {
     return b ? "true" : "false";
}

        很简单,就是一个三元表达式,返回true或false的字符串。

    除此之外,还包括一些基本数据类型的包装类的valueOf方法,其底层都是调用对应的toString方法实现的,由于涉及到其他类的源码知识,这里不做介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值