美文网首页
[Java8源码阅读]String

[Java8源码阅读]String

作者: 薛定谔的猫病 | 来源:发表于2017-10-10 17:13 被阅读0次

    Java中String不是基本数据类型,而是一种特殊的类。

    String代表的是不可变的字符序列(被final修饰),为不可变对象,一旦被创建,就不能修改它的值,对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去。

    源码分析

    属性

    String中有两个较为重要的属性:

    private final char value[];
    private int hash;
    

    从value[]可以看出,String是通过字符数组的方式实现的。hash用于保存当前字符串的hash值。

    构造方法

    // 通过该构造函数的String值为空字符串
    public String()
    // 使用字符串构造函数
    public String(String original)
    // 字符串数组构造函数
    public String(char value[])
    // 从传入value数组中offset位置(包含offset)开始,截取count个字符构造String
    public String(char value[], int offset, int count)
    // 基本同上
    public String(int[] codePoints, int offset, int count)
    // 下面两个为过时构造函数
    public String(byte ascii[], int hibyte, int offset, int count)
    public String(byte ascii[], int hibyte)
    // 使用byte[]字节数组构造String
    public String(byte bytes[], int offset, int length, String charsetName)
    public String(byte bytes[], int offset, int length, Charset charset)
    public String(byte bytes[], String charsetName)
    public String(byte bytes[], Charset charset)
    public String(byte bytes[], int offset, int length)
    public String(byte bytes[])
    // 使用StringBuffer和StringBuilder构造String
    public String(StringBuffer buffer)
    public String(StringBuilder builder)
    /** 
     * 保护类型的构造函数,这个构造函数比较特别
     * 1. 传入share并未使用,share主要作用是为了和上面String(char[] value)做区别
     * 2. String(char[] value)方法在创建String的时候会用到 会用到Arrays的copyOf方法将value中的内容逐一复制到String当中,而这个String(char[] value, boolean share)方法则是直接将value的引用赋值给String的value。那么也就是说,这个方法构造出来的String和参数传过来的char[] value共享同一个数组
     **/
    String(char[] value, boolean share){
        this.value = value;
    }
    

    方法

    charAt、codePointAt、codePointBefore、codePointCount、offsetByCodePoints

    获取并返回索引对应的字符:

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

    获取并返回索引对应字符的Unicode编码:

    public int codePointAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return Character.codePointAtImpl(value, index, value.length);
    }
    

    获取并返回给定索引前面的Unicode代码点:

    public int codePointBefore(int index) {
        int i = index - 1;
        if ((i < 0) || (i >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return Character.codePointBeforeImpl(value, index, 0);
    }
    

    准确计算unicode字符的数量:

    public int codePointCount(int beginIndex, int endIndex) {
        if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) {
            throw new IndexOutOfBoundsException();
        }
        return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
    }
    

    获取索引偏移后指定代码点的索引:

    public int offsetByCodePoints(int index, int codePointOffset) {
        if (index < 0 || index > value.length) {
            throw new IndexOutOfBoundsException();
        }
        return Character.offsetByCodePointsImpl(value, 0, value.length,
                index, codePointOffset);
    }
    

    getBytes

    在创建String的时候,可以使用byte[]数组,将一个字节数组转换成字符串,同样,我们可以将一个字符串转换成字节数组,那么String提供了很多重载的getBytes方法。但是,值得注意的是,在使用这些方法的时候一定要注意编码问题。比如:

    String s = "你好,世界!"; 
    byte[] bytes = s.getBytes();
    

    这段代码在不同的平台上运行得到结果是不一样的。由于我们没有指定编码方式,所以在该方法对字符串进行编码的时候就会使用系统的默认编码方式,比如在中文操作系统中可能会使用GBK或者GB2312进行编码,在英文操作系统中有可能使用iso-8859-1进行编码。这样写出来的代码就和机器环境有很强的关联性了,所以,为了避免不必要的麻烦,我们要指定编码方式。如使用以下方式:

    String s = "你好,世界!"; 
    byte[] bytes = s.getBytes("utf-8");
    

    replace、replaceAll、replaceFirst

    • replace的参数是char和CharSequence,即可以支持字符的替换,也支持字符串的替换
    • replaceAll和replaceFirst的参数是regex,即基于规则表达式的替换,比如,可以通过replaceAll(“\d”, “*”)把一个字符串所有的数字字符都换成星号;

    相同点是都是全部替换,即把源字符串中的某一字符或字符串全部换成指定的字符或字符串, 如果只想替换第一次出现的,可以使用 replaceFirst(),这个方法也是基于规则表达式的替换,但与replaceAll()不同的是,只替换第一次出现的字符串;

    另外,如果replaceAll()和replaceFirst()所用的参数据不是基于规则表达式的,则与replace()替换字符串的效果是一样的,即这两者也支持字符串的操作;

    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;
    }
    
    public String replaceAll(String regex, String replacement) {
        return Pattern.compile(regex).matcher(this).replaceAll(replacement);
    }
    
    public String replaceFirst(String regex, String replacement) {
        return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
    }
    

    split

    按照字符regex将字符串分成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);
    }
    

    equals、contentEquals、equalsIgnoreCase

    equals:

    1. 如果两个对象指向地址值一样,就返回true;
    2. 判断传入类型是否为String类型
    3. 先判断长度是否一样,在循环判断每个字符是否相等
    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;
    }
    

    contentEquals:
    接收StringBuffer对象,比较两个内容是否相等

    public boolean contentEquals(StringBuffer sb) {
        return contentEquals((CharSequence)sb);
    }
    
    public boolean contentEquals(CharSequence cs) {
        // Argument is a StringBuffer, StringBuilder
        if (cs instanceof AbstractStringBuilder) {
            if (cs instanceof StringBuffer) {
                synchronized(cs) {
                   return nonSyncContentEquals((AbstractStringBuilder)cs);
                }
            } else {
                return nonSyncContentEquals((AbstractStringBuilder)cs);
            }
        }
        // Argument is a String
        if (cs instanceof String) {
            return equals(cs);
        }
        // Argument is a generic CharSequence
        char v1[] = value;
        int n = v1.length;
        if (n != cs.length()) {
            return false;
        }
        for (int i = 0; i < n; i++) {
            if (v1[i] != cs.charAt(i)) {
                return false;
            }
        }
        return true;
    }
    

    equalsIgnoreCase:忽略字符串大小写进行比较是否相等

    public boolean equalsIgnoreCase(String anotherString) {
        return (this == anotherString) ? true
                : (anotherString != null)
                && (anotherString.value.length == value.length)
                && regionMatches(true, 0, anotherString, 0, value.length);
    }
    

    compareTo:比较两个字符串大小

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

    subsString

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

    intern

    public native String intern();
    

    该方法返回一个字符串对象的内部化引用。 String类维护一个初始为空的字符串的对象池,当intern方法被调用时,如果对象池中已经包含这一个相等的字符串对象则返回对象池中的实例,否则添加字符串到对象池并返回该字符串的引用。

    相关面试题

    如果自己写一个String类,路径与Java中String类路径相同,会发生什么?

    这题和Java的类加载机制有关,Java采用双亲委派机制,如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
    所有对于用户自定义的String类,不会被加载,当然,也不会被使用。

    String,StringBuilder,StringBuffer的区别?

    String是字符串常量,final修饰;StringBuffer字符串变量(线程安全);
    StringBuilder 字符串变量(线程不安全).

    • String和StringBuffer

    String和StringBuffer主要区别是性能:String是不可变对象,每次对String类型进行操作都等同于产生了一个新的String对象,然后指向新的String对象.所以尽量不在对String进行大量的拼接操作,否则会产生很多临时对象,导致GC开始工作,影响系统性能.

    StringBuffer是对对象本身操作,而不是产生新的对象,因此在有大量拼接的情况下,建议使用StringBuffer.

    但是需要注意现在JVM会对String拼接做一定的优化:

    String s = “This is only ” + ”simple” + ”test”
    

    会被虚拟机直接优化成String s=“This is only simple test”,此时就不存在拼接过程.

    • StringBuffer和StringBuilder

    StringBuffer是线程安全的可变字符串,其内部实现是可变数组.StringBuilder是jdk 1.5新增的,其功能和StringBuffer类似,但是非线程安全.因此,在没有多线程问题的前提下,使用StringBuilder会取得更好的性能.

    你对String对象的intern()熟悉么?

    intern()方法会首先从常量池中查找是否存在该常量值,如果常量池中不存在则现在常量池中创建,如果已经存在则直接返回.
    比如 :

    String s1=”aa”; 
    String s2=s1.intern(); 
    System.out.print(s1==s2);//返回true
    

    参考资料:

    Java 7 源码学习系列(一)——String
    Java7为什么要修改substring的实现
    JDK源码分析之String篇

    相关文章

      网友评论

          本文标题:[Java8源码阅读]String

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