美文网首页
String源码浅析

String源码浅析

作者: lvlvforever | 来源:发表于2018-06-20 18:08 被阅读0次

    Java中的String类平时用的多,但对其内部实现机制并不了解,本着知其然,更要知其所以然的学习态度,今天就研究下String源码。以1.7.0_80为例。

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

    这是String类的定义,final说明该类不允许被继承,如果有一个String的引用,那么肯定是String的引用,而不会是其他的类。接口方面实现了以下三个接口:

    1. Serializable:序列化接口,该接口无任何方法和域,仅用于说明此类可以序列化。
    2. Comparable<String>:用来实现字符串比较,需要实现其compareTo(T o)方法。
    3. CharSequence:该接口代表了一个只读的字符序列。
    成员变量
    /** 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;
    

    主要的成员变量就是上面的三个,value数组是用来保存字符串的,也就是字符串是以char[]数组的方式保存的,使用了private修饰符,而且并没有提供任何其他访问方法,使用了final关键字修饰,使得value的引用不可改变(引用的具体内容是可以改变的,不过因为外部无法获取value引用,所以也无法改变其内容)。
    hash保存了字符串的hashcode值,将其缓存起来,提高效率。

    构造方法

    看几个构造方法:

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

    这个方法仅仅是将original字符串的value引用和hash复制了一下,因为string的不可变性,所以使用这一方法没有必要。

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

    这个方法是复制了一份字符数组给value。

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

    所有的构造方法均是给value设置值。

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

    length()和isEmpty()都是使用的value数组的属性。

    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(Object anObject)方法写的很经典,首先判断这两个对象地址是否一样,如果一样那肯定就相等了,其次判断anObject是否是string类型的,然后对其中的value数组进行长度判断,通过后逐一字符是否相等。

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

    hashcode计算利用了 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]公式
    现在IDE都可以自动生成equals(Object o)和hashCode()方法。

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

    字符串比较方法,按照ascii码的顺序和长度进行比较。

    public int compareToIgnoreCase(String str) {
            return CASE_INSENSITIVE_ORDER.compare(this, str);
        }
    

    使用了一个静态内部类来做忽略字符大小写的比较。

    public String concat(String str) {
            int otherLen = str.length();
            if (otherLen == 0) {
                return this;
            }
            int len = value.length;
            char buf[] = Arrays.copyOf(value, len + otherLen);
            str.getChars(buf, len);
            return new String(buf, true);
        }
    

    连接字符串,如果str是空的,就返回当前字符串,否则就使用Arrays.copy()在建立一个buf数组,将str也保存到buf中,在建立一个新的字符串。

    static int indexOf(char[] source, int sourceOffset, int sourceCount,
                char[] target, int targetOffset, int targetCount,
                int fromIndex) {
            if (fromIndex >= sourceCount) {
                return (targetCount == 0 ? sourceCount : -1);
            }
            if (fromIndex < 0) {
                fromIndex = 0;
            }
            if (targetCount == 0) {
                return fromIndex;
            }
    
            char first = target[targetOffset];
            int max = sourceOffset + (sourceCount - targetCount);
    
            for (int i = sourceOffset + fromIndex; i <= max; i++) {
                /* Look for first character. */
                if (source[i] != first) {
                    while (++i <= max && source[i] != first);
                }
    
                /* Found first character, now look at the rest of v2 */
                if (i <= max) {
                    int j = i + 1;
                    int end = j + targetCount - 1;
                    for (int k = targetOffset + 1; j < end && source[j]
                            == target[k]; j++, k++);
    
                    if (j == end) {
                        /* Found whole string. */
                        return i - sourceOffset;
                    }
                }
            }
            return -1;
        }
    

    这个是查找子串的方法,这里使用了最基础的查找,首先找到第一个匹配的字符,接着看剩下的字符是否匹配,如果不匹配,则从下一个字符重新查找。这里为啥不使用kmp算法呢?这里有说明。stackoverflow
    大概意思就是一般字符串比较短,这种暴力算法就可以了,如果字符串很长,那么会用其他的数据结构来做,KMP算法有一定的预处理操作以及空间占用。

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

    获取子串。这个方法在1.7里进行了调整,之前的版本里string里有一个offset和count成员变量,来标识此字符串是value数组里的哪段。这里导致内存泄漏的问题,在对长字符串进行substring()操作时,直接修改了offset和count属性,这种模式是享元模式,两个字符串共享同一个底层数组,当长字符串被回收后,子串会保留长字符串的完整的数组,所以会出现内存泄漏问题。1.7里通过复制所需要的数组来解决这个问题,虽然多了些空间消耗,但解决了内存泄漏的问题。

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

    消除字符串两端空白字符。

    总结下,String类实质是一个private final char[] value数组,所有的都是围绕value来处理的。

    相关文章

      网友评论

          本文标题:String源码浅析

          本文链接:https://www.haomeiwen.com/subject/rokxyftx.html