精华内容
下载资源
问答
  • String源码解析

    千次阅读 多人点赞 2020-11-15 11:58:35
    本章源码分析基于JDK1.7 实现的接口 String类被final修饰词修饰,代表不可修改的特性,它实现了三个接口,Serializable是序列化接口,Compareble是排序接口,Char是字符序列接口。 public final class String ...

    本章源码分析基于JDK1.7

    实现的接口

    String类被final修饰词修饰,代表不可修改的特性,它实现了三个接口,Serializable是序列化接口,Compareble是排序接口,Char是字符序列接口。

    public final class String implements Serializable, Comparable<String>, CharSequence 

    主要成员变量

    char[]:String通过char[]来实现String的各种功能,字符串由字符数组实现。

    hash:用于缓存hash值,因为String类是final不可修改的,所以hash值也是固定的,为了避免重复计算hash值而缓存。

    CASE_INSENSITIVE_ORDER:排序器,由上可知String类实现了Compareble接口,这里的Comparator用于忽视大小写的字符串的比较。

        private final char[] value;
        private int hash;
        public static final Comparator<String> CASE_INSENSITIVE_ORDER = new String.CaseInsensitiveComparator();

    构造函数

    String共有15个重载构造函数,入参这几种:空、char[]、String、StringBuffer、StringBuilder、byte[],通过这些来构造字符串对象。

        //第一种,入参为空,新建了大小为0的char数组,这就是空字符串
        public String() {
            this.hash32 = 0;
            this.value = new char[0];
        }
    
        //第二种,入参为String对象,直接将入参的属性复制过来
        public String(String var1) {
            this.hash32 = 0;
            this.value = var1.value;
            this.hash = var1.hash;
        }
    
        //第三种,入参为char[],将value赋值为入参var1
        public String(char[] var1) {
            this.hash32 = 0;
            this.value = Arrays.copyOf(var1, var1.length);
        }
    
        //第四种,入参为char[],截取char[]中从var2到var3的字符作为字符串
        public String(char[] var1, int var2, int var3) {
            this.hash32 = 0;
            if (var2 < 0) {
                throw new StringIndexOutOfBoundsException(var2);
            } else if (var3 < 0) {
                throw new StringIndexOutOfBoundsException(var3);
            } else if (var2 > var1.length - var3) {
                throw new StringIndexOutOfBoundsException(var2 + var3);
            } else {
                this.value = Arrays.copyOfRange(var1, var2, var2 + var3);
            }
        }
    
        
        public String(int[] var1, int var2, int var3) {
            this.hash32 = 0;
            if (var2 < 0) {
                throw new StringIndexOutOfBoundsException(var2);
            } else if (var3 < 0) {
                throw new StringIndexOutOfBoundsException(var3);
            } else if (var2 > var1.length - var3) {
                throw new StringIndexOutOfBoundsException(var2 + var3);
            } else {
                int var4 = var2 + var3;
                int var5 = var3;
    
                int var7;
                for(int var6 = var2; var6 < var4; ++var6) {
                    var7 = var1[var6];
                    if (!Character.isBmpCodePoint(var7)) {
                        if (!Character.isValidCodePoint(var7)) {
                            throw new IllegalArgumentException(Integer.toString(var7));
                        }
    
                        ++var5;
                    }
                }
    
                char[] var10 = new char[var5];
                var7 = var2;
    
                for(int var8 = 0; var7 < var4; ++var8) {
                    int var9 = var1[var7];
                    if (Character.isBmpCodePoint(var9)) {
                        var10[var8] = (char)var9;
                    } else {
                        Character.toSurrogates(var9, var10, var8++);
                    }
    
                    ++var7;
                }
    
                this.value = var10;
            }
        }
    
        /** @deprecated */
        @Deprecated
        public String(byte[] var1, int var2, int var3, int var4) {
            this.hash32 = 0;
            checkBounds(var1, var3, var4);
            char[] var5 = new char[var4];
            int var6;
            if (var2 == 0) {
                for(var6 = var4; var6-- > 0; var5[var6] = (char)(var1[var6 + var3] & 255)) {
                    ;
                }
            } else {
                var2 <<= 8;
    
                for(var6 = var4; var6-- > 0; var5[var6] = (char)(var2 | var1[var6 + var3] & 255)) {
                    ;
                }
            }
    
            this.value = var5;
        }
    
        /** @deprecated */
        @Deprecated
        public String(byte[] var1, int var2) {
            this(var1, var2, 0, var1.length);
        }
    
        private static void checkBounds(byte[] var0, int var1, int var2) {
            if (var2 < 0) {
                throw new StringIndexOutOfBoundsException(var2);
            } else if (var1 < 0) {
                throw new StringIndexOutOfBoundsException(var1);
            } else if (var1 > var0.length - var2) {
                throw new StringIndexOutOfBoundsException(var1 + var2);
            }
        }
    
        public String(byte[] var1, int var2, int var3, String var4) throws UnsupportedEncodingException {
            this.hash32 = 0;
            if (var4 == null) {
                throw new NullPointerException("charsetName");
            } else {
                checkBounds(var1, var2, var3);
                this.value = StringCoding.decode(var4, var1, var2, var3);
            }
        }
    
        public String(byte[] var1, int var2, int var3, Charset var4) {
            this.hash32 = 0;
            if (var4 == null) {
                throw new NullPointerException("charset");
            } else {
                checkBounds(var1, var2, var3);
                this.value = StringCoding.decode(var4, var1, var2, var3);
            }
        }
    
        public String(byte[] var1, String var2) throws UnsupportedEncodingException {
            this(var1, 0, var1.length, (String)var2);
        }
    
        public String(byte[] var1, Charset var2) {
            this(var1, 0, var1.length, (Charset)var2);
        }
    
        public String(byte[] var1, int var2, int var3) {
            this.hash32 = 0;
            checkBounds(var1, var2, var3);
            this.value = StringCoding.decode(var1, var2, var3);
        }
    
        public String(byte[] var1) {
            this((byte[])var1, 0, var1.length);
        }
    
        public String(StringBuffer var1) {
            this.hash32 = 0;
            synchronized(var1) {
                this.value = Arrays.copyOf(var1.getValue(), var1.length());
            }
        }
    
        public String(StringBuilder var1) {
            this.hash32 = 0;
            this.value = Arrays.copyOf(var1.getValue(), var1.length());
        }
    
        String(char[] var1, boolean var2) {
            this.hash32 = 0;
            this.value = var1;
        }
    
        /** @deprecated */
        @Deprecated
        String(int var1, int var2, char[] var3) {
            this(var3, var1, var2);
        }
    

    length方法

    通过获取char[]的长度来获取字符串的长度

        public int length() {
            return this.value.length;
        }

    isEmpty方法

    通过判断char[]的长度是否为0来判断是否为空

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

    charAt方法

    通过char[]数组下标获取到对应位置的char字符

        public char charAt(int var1) {
            if (var1 >= 0 && var1 < this.value.length) {
                return this.value[var1];
            } else {
                throw new StringIndexOutOfBoundsException(var1);
            }
        }

    equals方法

    首先比较内存地址,再判断是否是String类型,然后再判断长度,最后逐个比较其中的char。

        public boolean equals(Object var1) {
            //首先比较内存地址
            if (this == var1) {
                return true;
            } else {
                //判断var1是否是String类型
                if (var1 instanceof String) {
                    //如果是则强转
                    String var2 = (String)var1;
                    //获取当前String中char[]的长度
                    int var3 = this.value.length;
                    //如果传入的var1和当前String中char[]的长度一样
                    if (var3 == var2.value.length) {
                        
                        char[] var4 = this.value;
                        char[] var5 = var2.value;
                        //将传入的var1和当前字符串中char[]中字符逐个比较,若有一个不一致则返回false
                        for(int var6 = 0; var3-- != 0; ++var6) {
                            if (var4[var6] != var5[var6]) {
                                return false;
                            }
                        }
    
                        return true;
                    }
                }
    
                return false;
            }
        }

    hashCode方法

    这里的hash值计算有个特点,就是String内部缓存了hash值,如果hash值不为0则直接返回,不需要再次进行计算,因为String是被final修饰的,它不会被修改,所以没有必要每次都重新计算hash值。

        public int hashCode() {
            //首先从String的成员变量hash获取到hash值
            int var1 = this.hash;
            //如果hash值为0且当前String不为空
            if (var1 == 0 && this.value.length > 0) {
                //获取到当前String的char[]
                char[] var2 = this.value;
    
                //逐个使用char循环叠加计算hash值
                for(int var3 = 0; var3 < this.value.length; ++var3) {
                    var1 = 31 * var1 + var2[var3];
                }
    
                //计算好后将hash值赋值给成员变量hash
                this.hash = var1;
            }
    
            //最后返回hash值
            return var1;
        }

    compareTo方法

    通过两个字符串的第一个不一样的字符来比较大小并返回结果,若两个字符串的字符都一样则比较两个字符串的长度。

        public int compareTo(String str) {
            //分别获取到当前String和传入String的length
            int thisLen = this.value.length;
            int strLen = str.value.length;
            //计算出两个String最小的长度minLen
            int minLen = Math.min(thisLen, strLen);
            char[] thisValue = this.value;
            char[] strValue = str.value;
    
            //循环找出两个字符串第一个不一样的字符比较大小并返回比较结果
            for(int i = 0; i < minLen; ++i) {
                char thisChar = thisValue[i];
                char strChar = strValue[i];
                if (thisChar != strChar) {
                    return thisChar - strChar;
                }
            }
        
            //若两个字符串循环比较的字符是一样的,那么使用字符串长度来比较大小
            return thisLen - strLen;
        }

     

    展开全文
  • String 源码解析,深入认识String

    千次阅读 多人点赞 2017-02-26 21:53:16
    String 源码浅析

    问题

    前些日子犯了一个很低级的错误,将集合A==B,然后将B拿过去使用,发现事情不对,集合A的元素也发生了变化。好尴尬啊,这就是对==号的理解不深导致的低级错误。正好上一篇写了一个Stringutils类。那么今天就让我们深入她,理解她吧。

    引入

    public class VeryGood {
    
    
    
        public static void main(String[] args) {
            String a = "a"+"b"+1;
            String b = "ab1";
            System.out.println(a == b);
    
    
            String a1 = new String("ab1");
            String b1 = "ab1";
            System.out.println(a1 == b1);
        }
    
    
    }

    他们的运行结果是什么呢?

    正确答案是:

    true
    false

    让我们看看经过编译器编译后的代码:

    //第一段代码
    public void stringTest() {
        String a = "ab1";
        String b = "ab1";
        System.out.println(a == b);
    }
    
    //第二段代码
    public void stringTest() {
        String a1 = new String("ab1");
        String b = "ab1";
        System.out.println(a1 == b);
    }

    也就是说第一段代码经过了编译期优化,原因是编译器发现”a”+”b”+1和”ab1”的效果是一样的,都是不可变量组成。但是为什么他们的内存地址会相同呢?一起看看String类的一些重要源码吧。

    String类

    String类被final所修饰,也就是说String对象是不可变量,并发程序最喜欢不可变量了。String类实现了Serializable, Comparable, CharSequence接口。

    Comparable接口有compareTo(String s)方法,CharSequence接口有length(),charAt(int index),subSequence(int start,int end)方法。

    String属性

    String类中包含一个不可变的char数组用来存放字符串,一个int型的变量hash用来存放计算后的哈希值。

    /**
     * The <code>String</code> class represents character strings. All
     * string literals in Java programs, such as <code>"abc"</code>, are
     * implemented as instances of this class.
     * <p>
     * Strings are constant; their values cannot be changed after they
     * are created. String buffers support mutable strings.
     * /

    String构造函数

    //不含参数的构造函数,一般没什么用,因为value是不可变量
    public String() {
        this.value = new char[0];
    }
    
    //参数为String类型
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
    
    //参数为char数组,使用java.utils包中的Arrays类复制
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    
    //从bytes数组中的offset位置开始,将长度为length的字节,以charsetName格式编码,拷贝到value
    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);
    }
    
    //调用public String(byte bytes[], int offset, int length, String charsetName)构造函数
    public String(byte bytes[], String charsetName)
            throws UnsupportedEncodingException {
        this(bytes, 0, bytes.length, charsetName);
    }

    String常用方法

    boolean equals(Object anObject)
    
    public boolean equals(Object anObject) {
        //如果引用的是同一个对象,返回真
        if (this == anObject) {
            return true;
        }
        //如果不是String类型的数据,返回假
        if (anObject instanceof String) {
            String anotherString = (String) anObject;
            int n = value.length;
            //如果char数组长度不相等,返回假
            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方法经常用得到,它用来判断两个对象从实际意义上是否相等,String对象判断规则:

    内存地址相同,则为真。

    如果对象类型不是String类型,则为假。否则继续判断。

    如果对象长度不相等,则为假。否则继续判断。

    从后往前,判断String类中char数组value的单个字符是否相等,有不相等则为假。如果一直相等直到第一个数,则返回真。

    由此可以看出,如果对两个超长的字符串进行比较还是非常费时间的。

    int compareTo(String anotherString)
    
    public int compareTo(String anotherString) {
        //自身对象字符串长度len1
        int len1 = value.length;
        //被比较对象字符串长度len2
        int len2 = anotherString.value.length;
        //取两个字符串长度的最小值lim
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;
    
        int k = 0;
        //从value的第一个字符开始到最小长度lim处为止,如果字符不相等,返回自身(对象不相等处字符-被比较对象不相等字符)
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        //如果前面都相等,则返回(自身长度-被比较对象长度)
        return len1 - len2;
    }

    这个方法写的很巧妙,先从0开始判断字符大小。如果两个对象能比较字符的地方比较完了还相等,就直接返回自身长度减被比较对象长度,如果两个字符串长度相等,则返回的是0,巧妙地判断了三种情况。

    int hashCode()
    
    public int hashCode() {
        int h = hash;
        //如果hash没有被计算过,并且字符串不为空,则进行hashCode计算
        if (h == 0 && value.length > 0) {
            char val[] = value;
    
            //计算过程
            //s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            //hash赋值
            hash = h;
        }
        return h;
    }

    String类重写了hashCode方法,Object中的hashCode方法是一个Native调用。String类的hash采用多项式计算得来,我们完全可以通过不相同的字符串得出同样的hash,所以两个String对象的hashCode相同,并不代表两个String是一样的。

    boolean startsWith(String prefix,int toffset)
    
    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;
        // Note: toffset might be near -1>>>1.
        //如果起始地址小于0或者(起始地址+所比较对象长度)大于自身对象长度,返回假
        if ((toffset < 0) || (toffset > value.length - pc)) {
            return false;
        }
        //从所比较对象的末尾开始比较
        while (--pc >= 0) {
            if (ta[to++] != pa[po++]) {
                return false;
            }
        }
        return true;
    }
    
    public boolean startsWith(String prefix) {
        return startsWith(prefix, 0);
    }
    
    public boolean endsWith(String suffix) {
        return startsWith(suffix, value.length - suffix.value.length);
    }

    起始比较和末尾比较都是比较经常用得到的方法,例如在判断一个字符串是不是http协议的,或者初步判断一个文件是不是mp3文件,都可以采用这个方法进行比较。

    String concat(String 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);
    }

    concat方法也是经常用的方法之一,它先判断被添加字符串是否为空来决定要不要创建新的对象。

    String replace(char oldChar,char newChar)
    
    public String replace(char oldChar, char newChar) {
        //新旧值先对比
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */
    
            //找到旧值最开始出现的位置
            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            //从那个位置开始,直到末尾,用新值代替出现的旧值
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }

    这个方法也有讨巧的地方,例如最开始先找出旧值出现的位置,这样节省了一部分对比的时间。replace(String oldStr,String newStr)方法通过正则表达式来判断。

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

    trim方法用起来也6的飞起

    String intern()
    
    public native String intern();

    intern方法是Native调用,它的作用是在方法区中的常量池里通过equals方法寻找等值的对象,如果没有找到则在常量池中开辟一片空间存放字符串并返回该对应String的引用,否则直接返回常量池中已存在String对象的引用。

    将引言中第二段代码

    //String a = new String("ab1");
    //改为
    String a = new String("ab1").intern();

    则结果为为真,原因在于a所指向的地址来自于常量池,而b所指向的字符串常量默认会调用这个方法,所以a和b都指向了同一个地址空间。

    int hash32()
    
    private transient int hash32 = 0;
    int hash32() {
        int h = hash32;
        if (0 == h) {
           // harmless data race on hash32 here.
           h = sun.misc.Hashing.murmur3_32(HASHING_SEED, value, 0, value.length);
    
           // ensure result is not zero to avoid recalcing
           h = (0 != h) ? h : 1;
    
           hash32 = h;
        }
    
        return h;
    }

    在JDK1.7中,Hash相关集合类在String类作key的情况下,不再使用hashCode方式离散数据,而是采用hash32方法。这个方法默认使用系统当前时间,String类地址,System类地址等作为因子计算得到hash种子,通过hash种子在经过hash得到32位的int型数值。

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

    以上是一些简单的常用方法。

    总结

    String对象是不可变类型,返回类型为String的String方法每次返回的都是新的String对象,除了某些方法的某些特定条件返回自身。

    String对象的三种比较方式:

    ==内存比较:直接对比两个引用所指向的内存值,精确简洁直接明了。

    equals字符串值比较:比较两个引用所指对象字面值是否相等。

    hashCode字符串数值化比较:将字符串数值化。两个引用的hashCode相同,不保证内存一定相同,不保证字面值一定相同。

    展开全文
  • Java String源码解析

    千次阅读 2016-05-30 10:14:31
    String类概要 所有的字符串字面量都属于String类,String对象创建后不可改变,因此可以缓存共享,StringBuilder,StringBuffer是可变的实现 String类提供了操作字符序列中单个字符的方法,比如有比较字符串,搜索...

    String类概要

    • 所有的字符串字面量都属于String类,String对象创建后不可改变,因此可以缓存共享,StringBuilder,StringBuffer是可变的实现
    • String类提供了操作字符序列中单个字符的方法,比如有比较字符串,搜索字符串等
    • Java语言提供了对字符串连接运算符的特别支持(+),该符号也可用于将其他类型转换成字符串。
    • 字符串的连接实际上是通过StringBuffer或者StringBuilder的append()方法来实现的
    • 一般情况下,传递一个空参数在这类构造函数或方法会导致NullPointerException异常被抛出。
    • String表示一个字符串通过UTF-16(unicode)格式,补充字符通过代理对表示。索引值参考字符编码单元,所以补充字符在String中占两个位置。

    String是不可变的

    • String是常量,一旦被创建就不可被改变,因此可以用来共享

    从String的怪异现象讲起

    String是否相等

    ==判断的是对象的内存起始地址是否相同,equals判断自定义的语义是否相同

    • JVM为了提高内存效率,将所有不可变的字符串缓存在常量池中,当有新的不可变的字符串需要创建时,如果常量池中存在相等的字符串就直接将引用指向已有的字符串常量,而不会创建新对象
    • new创建的对象存储在堆内存,不可能与常量区的对象具有相同地址
    • 直接用字面量初始化String要比用new 关键字创建String对象效率更高
    public class Demo {
        public static void main(String[] args) throws Exception {
            String s = "abc";
            String s1 = "abc";
            String s2 = "a" + "bc";
            final String str1 = "a";
            final String str2 = "bc";
            String s3 = str1 + str2;
            String s4 = new String("abc");
            System.out.println(s == s1);
            System.out.println(s == s2);
            System.out.println(s == s3);
            System.out.println(s == s4);
        }
    } //结果:true    true    true    false

    为什么String不可变

    final修饰变量,如果是基本类型那么内容运行期间不可变,如果是引用类型那么引用的对象(包括数组)运行期地址不可变,但是对象(数组)的内容是可以改变的

    • final只是保证value不会指向其他的数组,但不保证数组内容不可修改
    • private属性保证了不可以在类外访问数组,也就不能改变其内容
    • String内部没有改变value内容的函数,所以String就不可变了
    • String声明为final杜绝了通过继承的方法添加新的函数
    • 基于数组的构造方法,会拷贝数组元素,从而避免了通过外部引用修改value的情况
    • 用String构造其他可变对象时,涉及的数组只是返回的数组的拷贝而不是原数组,例如 new StringBuilder(str),会把str数组进行拷贝后传递给StringBuilder而不是传递原数组

    当然只要类库设计人愿意,只要增加一个类似的setCharAt(index)的接口,String就变成可变的了

        private final char value[];
        private int hash; // Default to 0  
        public String(char value[]) {
            this.value = Arrays.copyOf(value, value.length);
        }  

    通过反射改变String

    • final 只在编译器有效,在运行期间无效,因此可以通过反射改变value引用的对象
    • s与str始终具有相同的内存地址,反射改变了s的内容,并没有新创建对象
    • s 与 s1对应常量池中的两个对象,所以即便通过反射修改了s的内容,他们两个的内存地址还是不同的
    public class Demo {
        public static void main(String[] args) throws Exception {
            String s = "abc";
            String str = s;
            String s1 = "bbb";
            System.out.println(str == s);
            Field f = s.getClass().getDeclaredField("value");
            f.setAccessible(true);
            f.set(s, new char[]{'b', 'b', 'b'});
            System.out.println(str + "    " + s);
            System.out.println(s == str);
            System.out.println(s == s1);
        }
    }  //结果:bbb    bbb    true    false

    String的HashCode

    s的内容改变了但是hashCode值并没有改变,虽然s与s1的内容是相同的但是他们hashCode值并不相同

    • Object的hashCode方法返回的是16进制内存地址,String类重写了hashCode的,hashCode值的计算是基于字符串内容的
    • String的hashCode值初始为0,由于String是不可变的,当第一次运行完hashCode方法后String类对HashCode值进行了缓存,下一次在调用时直接返回hash值
    public class Demo {
        public static void main(String[] args) throws Exception {
            String s = "abc";
            String s1 = "bbb";
            System.out.println(s.hashCode());
            Field f = s.getClass().getDeclaredField("value");
            f.setAccessible(true);
            f.set(s, new char[]{'b', 'b', 'b'});
            System.out.println(s + "    "+ s1);
            System.out.println(s.hashCode() +" " +s1.hashCode());
        }
    }  //结果:96354    bbb    bbb    96354 97314

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

    toString方法中的this

    • Java为String类重载了“+”操作符,String类与其他类对象进行连接时会调用其他类的toString方法
    • 如果在其他类的toString方法中用“+”对this进行连接就会出现无限递归调用而出现栈溢出错误
    • 解决方法将this换做super.this
    public class Demo {
        @Override
        public String toString() {
            //会造成递归调用
    //        return "address"+super.toString();
            return "address"+super.toString();
        }
        public static void main(String[] args) {
            System.out.println(new Demo());
        }
    }  

    CodePoints与CodeUnit

    String的length表示的是代码单元的个数,而不是字符的个数

    • codePoints是代码点, 表示的是例如’A’, ‘王’ 这种字符,每种字符都有一个唯一的数字编号,这个数字编号就叫unicode code point。目前code point的数值范围是0~0x10FFFF。
    • codeUnit是代码单元, 它根据编码不同而不同, 可以理解为是字符编码的基本单元,java中的char是两个字节, 也就是16位的。这样也反映了一个char只能表示从u+0000~u+FFFF范围的unicode字符, 在这个范围的字符也叫BMP(basic Multiligual Plane ), 超出这个范围的叫增补字符,增补字符占用两个代码单元。
    public class Demo {
        public static void main(String[] args) {
            String s = "\u1D56B";
            System.out.println(s);
            System.out.println(s.length());
        }
    }  

    我们看看String是怎么处理增补字符的

    • 首先value字符数组的长度是根据代码单元来定的,每出现一个Surrogate字符数组长度在count的基础上加一
    • BMP字符直接存储,增补字符的用两个char分别存储高位和低位
        public String(int[] codePoints, 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 > codePoints.length - count) {
                throw new StringIndexOutOfBoundsException(offset + count);
            }
            final int end = offset + count;
            // Pass 1: Compute precise size of char[]
            int n = count;
            for (int i = offset; i < end; i++) {
                int c = codePoints[i];
                if (Character.isBmpCodePoint(c))
                    continue;
                else if (Character.isValidCodePoint(c))
                    n++;
                else throw new IllegalArgumentException(Integer.toString(c));
            }
            // Pass 2: Allocate and fill in char[]
            final char[] v = new char[n];
            for (int i = offset, j = 0; i < end; i++, j++) {
                int c = codePoints[i];
                if (Character.isBmpCodePoint(c))
                    v[j] = (char)c;
                else
                    Character.toSurrogates(c, v, j++);
            }
            this.value = v;
        }  
        static void toSurrogates(int codePoint, char[] dst, int index) {
            // We write elements "backwards" to guarantee all-or-nothing
            dst[index+1] = lowSurrogate(codePoint);
            dst[index] = highSurrogate(codePoint);
        }  

    源码解析

    声明

    • String类可序列化,可比较,实现CharSequence接口提供了对字符的基本操作
    • String内部使用final字符数组进行存储,涉及value数组的操作都使用了拷贝数组元素的方法,保证了不能在外部修改字符数组
    • String重写了Object的hashCode函数使hash值基于字符数组内容,但是由于String缓存了hash值,所以即便通过反射改变了字符数组内容,hashhashCode返回值不会自动更新
    • serialVersionUID 用来确定类的版本是否正确,如果不是同一个类会抛出InvalidCastException异常
    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence { 
        private final char value[];
        private static final long serialVersionUID = -6849794470754667710L;  
        /** Cache the hash code for the string */
        private int hash; // Default to 0
        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主要提供了通过String,StringBuilder,StringBuffer,char数组,int数组(CodePoint),byte数组(需要指定编码)进行初始化
    • 当通过字符串初始化字符串时,并没有执行value数组拷贝,因为original的value数组是不可以在外部修改的,也就保证了新String对象的不可修改
    • 通过字符数组,StringBuffer,StringBuilder进行初始化时,就要执行value数组元素的拷贝,创建新数组,防止外部对value内容的改变
    • 通过byte数组进行初始化,需要指定编码,或使用默认编码(ISO-8859-1),否则无法正确解释字节内容
    • 通过Unicode代码点进行的初始化,可能会包含非BMP字符(int值大于65535),这时候字符串的长度可能会长于int数组的长度,(见本文前面增补字符处理部分)
        public String(String original) {
            this.value = original.value;
            this.hash = original.hash;
        }  
        public String(StringBuffer buffer) {
            synchronized(buffer) {
                this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
            }
        }
        public String(StringBuilder builder) {
            this.value = Arrays.copyOf(builder.getValue(), builder.length());
        }  
        public String(char value[]) {
            this.value = Arrays.copyOf(value, value.length);
        }  
        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);
        } 
        public String(byte bytes[], int offset, int length, Charset charset) {
            if (charset == null)
                throw new NullPointerException("charset");
            checkBounds(bytes, offset, length);
            this.value =  StringCoding.decode(charset, bytes, offset, length);
        } 
        public String(byte bytes[], int offset, int length) {
            checkBounds(bytes, offset, length);
            this.value = StringCoding.decode(bytes, offset, length);
        }  
        static char[] decode(byte[] ba, int off, int len) {
            String csn = Charset.defaultCharset().name();
            try {
                // use charset name decode() variant which provides caching.
                return decode(csn, ba, off, len);
            } catch (UnsupportedEncodingException x) {
                warnUnsupportedCharset(csn);
            }
            try {
                return decode("ISO-8859-1", ba, off, len);
            } catch (UnsupportedEncodingException x) {
                // If this code is hit during VM initialization, MessageUtils is
                // the only way we will be able to get any kind of error message.
                MessageUtils.err("ISO-8859-1 charset not available: "
                                 + x.toString());
                // If we can not find ISO-8859-1 (a required encoding) then things
                // are seriously wrong with the installation.
                System.exit(1);
                return null;
            }
        } 

    内部构造函数

    使用外部数组来初始化String内部数组只有保证传入的数组不可能被改变才能保证String的不可变性,例如用String初始化String对象时

    • 这种方法使用共享value数组的方法避免了数组的拷贝,提高了效率
    • 上面分析指出如果直接使用外部传入的数组不能保证String的不可变性,这个方法只在String的内部使用,不能由外部调用
    • 添加share参数,只是为了重载构造函数,share必须为true
    • 该函数只用在不能缩短String长度的函数中,如concat(str1,str2),如果用在缩短String长度的函数如subString中会造成内存泄漏
        String(char[] value, boolean share) {
            // assert share : "unshared not supported";
            this.value = value;
        }  
        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);
        }  
            // 使用了Arrays.copyof方法来构造新的数组,拷贝元素,而不是共用数组
        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);
        }  

    如果String(value,share)可以在外部使用,就可以改变字符串内容

    public class Demo {
        public static void main(String[] args) {
            char[] arr = new char[] {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
            String s = new String(arr,true);
            arr[0] = 'a';
            System.out.println(s);
        }
    } 

    aLongString 已经不用了,但是由于其与aPart共享value数组,所以不能被回收,造成内存泄漏

         public String subTest(){
            String aLongString = "...a very long string..."; 
            String aPart = aLongString.substring(20, 40);
            return aPart;
        }

    主要方法

    其他主要方法

    length() 返回字符串长度
    
    isEmpty() 返回字符串是否为空
    
    charAt(int index) 返回字符串中第(index+1)个字符
    
    char[] toCharArray() 转化成字符数组
    
    trim() 去掉两端空格
    
    toUpperCase() 转化为大写
    
    toLowerCase() 转化为小写
    
    String concat(String str) //拼接字符串
    
    String replace(char oldChar, char newChar) //将字符串中的oldChar字符换成newChar字符
    
    //以上两个方法都使用了String(char[] value, boolean share);
    
    boolean matches(String regex) //判断字符串是否匹配给定的regex正则表达式
    
    boolean contains(CharSequence s) //判断字符串是否包含字符序列s
    
    String[] split(String regex, int limit) 按照字符regex将字符串分成limit份。
    
    String[] split(String regex)
    

    重载的valueOf方法

    可以看到主要是调用构造函数或者是调用对应类型的toString完成到字符串的转换

        public static String valueOf(boolean b) {
            return b ? "true" : "false";
        }
        public static String valueOf(char c) {
            char data[] = {c};
            return new String(data, true);
        }
        public static String valueOf(int i) {
            return Integer.toString(i);
        }
        public static String valueOf(long l) {
            return Long.toString(l);
        }
        public static String valueOf(float f) {
            return Float.toString(f);
        }
        public static String valueOf(double d) {
            return Double.toString(d);
        }  
        public static String valueOf(char data[], int offset, int count) {
            return new String(data, offset, count);
        } 
        public static String copyValueOf(char data[], int offset, int count) {
            // All public String constructors now copy the data.
            return new String(data, offset, count);
        } 

    字符串查找算法 indexOf

    可以看到String的字符串匹配算法使用的是朴素的匹配算法,即前向匹配,当遇到不匹配字符时,主串从下一个字符开始,字串从开始位置开始
    其他相关字符串匹配算法

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

    编码问题 getBytes

    • 字符串最终都是使用机器码以字节存储的,当我们将字符串转换为字节的时候也需要给定编码,同一个字符不同的编码就对应不同的字节
    • 如不指定编码,就会使用默认的编码ISO-8859-1进行编码
    • 编码时为了避免平台编码的干扰,应当指定确定的编码
        String s = "你好,世界!";
        byte[] bytes = s.getBytes("utf-8");  
    
        public byte[] getBytes(String charsetName)
                throws UnsupportedEncodingException {
            if (charsetName == null) throw new NullPointerException();
            return StringCoding.encode(charsetName, value, 0, value.length);
        } 
        static byte[] encode(String charsetName, char[] ca, int off, int len)
            throws UnsupportedEncodingException
        {
            StringEncoder se = deref(encoder);
            String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
            if ((se == null) || !(csn.equals(se.requestedCharsetName())
                                  || csn.equals(se.charsetName()))) {
                se = null;
                try {
                    Charset cs = lookupCharset(csn);
                    if (cs != null)
                        se = new StringEncoder(cs, csn);
                } catch (IllegalCharsetNameException x) {}
                if (se == null)
                    throw new UnsupportedEncodingException (csn);
                set(encoder, se);
            }
            return se.encode(ca, off, len);
        } 

    比较方法

    • 所有比较方法都是比较对应的字符数组的内容,后两个比较方法用来进行区段比较
    • 在进行数组比较时,如果可以通过长度进行初步判断,一般可以提高效率
        boolean equals(Object anObject);
        boolean contentEquals(StringBuffer sb);
        boolean contentEquals(CharSequence cs);
        boolean equalsIgnoreCase(String anotherString);
        int compareTo(String anotherString);
        int compareToIgnoreCase(String str);
        boolean regionMatches(int toffset, String other, int ooffset,int len)  //局部匹配
        boolean regionMatches(boolean ignoreCase, int toffset,String other, int ooffset, int len)   //局部匹配  
    
        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;
        }  

    替换函数 replace

    • 单字符替换会替换所有特定字符的出现
    • replace为普通(literal)替换,不用正则表达式
    • replaceFirst与replaceAll都使用了正则表达式
        public String replace(CharSequence target, CharSequence replacement) {
            return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
                    this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
        } 
        public String replaceFirst(String regex, String replacement) {
            return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
        }
        public String replaceAll(String regex, String replacement) {
            return Pattern.compile(regex).matcher(this).replaceAll(replacement);
        } 
        public String replace(char oldChar, char newChar) {
            if (oldChar != newChar) {
                int len = value.length;
                int i = -1;
                char[] val = value; /* avoid getfield opcode */
                while (++i < len) {
                    if (val[i] == oldChar) {
                        break;
                    }
                }
                if (i < len) {
                    char buf[] = new char[len];
                    for (int j = 0; j < i; j++) {
                        buf[j] = val[j];
                    }
                    while (i < len) {
                        char c = val[i];
                        buf[i] = (c == oldChar) ? newChar : c;
                        i++;
                    }
                    return new String(buf, true);
                }
            }
            return this;
        }

    常量池相关方法

    • 每当定义一个字符串字面量,字面量进行字符串连接,或者final的String字面量初始化的变量的连接的变量时都会检查常量池中是否有对应的字符串,如果有就不创建新的字符串,而是返回指向常量池对应字符串的引用
    • 所有通过new String(str)方式创建的对象都会存在与堆区,而非常量区
    • 普通变量的连接,由于不能在编译期确定下来,所以不会存储在常量区
    public native String intern();  

    运算符的重载

    • String对“+”运算符进行了重载,通过反编译我们看到重载是通过StringBuilder的append方法,及String的valueOf方法实现的
    • int值转String过程中(”“+i)这种方法实际为(new StringBuilder()).append(i).toString();,而另外两种都是调用Integer的静态方法Integer.toString完成
    // int转String的方法比较
    public class Demo {
        public static void main(String[] args) throws Exception {
            int i = 5;
            String i1 = "" + i;
            String i2 = String.valueOf(i);
            String i3 = Integer.toString(i);
        }
    } 
    // 原始代码
    public class Demo {
        public static void main(String[] args) throws Exception {
            String string="hollis";
            String string2 = string + "chuang";
        }
    } 
    //反编译代码
    public class Demo {
        public static void main(String[] args) throws Exception {
            String string = "hollis";
            String string2 = (new StringBuilder(String.valueOf(string))).append("chuang").toString();
        }
    }
    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 255,092
精华内容 102,036
关键字:

string源码解析