JDK源码:String

本文详细解析了Java中的String类。介绍了其概述,表明字符串是常量且线程安全;阐述类定义,它被final修饰且实现三个接口;还分析了主要字段、内部类、构造方法和主要方法,如判断字符串相等、计算hash值等方法的具体流程。

概述

Java中使用String类来表示字符串,每一个字符串都是String类的实例。因为String类是被final关键词修饰的,所以实际上字符串都是常亮,变切实线程安全的。

类定义

  String 类被声明为 final,说明它不能再被继承。同时它实现了三个接口,分别为 Serializable、Comparable 和 CharSequence。其中 Serializable 接口表明其可以序列化。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
复制代码

主要字段

  ==value==是一个字符数组,被声明为final,是一个不可变数组,字符串实际上就是由这样一个字符数组的形式存储。
  ==hash==存储的是String实例的hash值,但是只有第一次调用hashCode()方法会计算hash值,然后会缓存下hash值,下次可以直接调用。

    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
复制代码

内部类

  String内部只有一个内部类CaseInsensitiveComparator,实现了Comparator, java.io.Serializable接口,分别提供比较器功能和序列化功能。

    private static class CaseInsensitiveComparator
            implements Comparator<String>, java.io.Serializable
复制代码

  比较器内部主要的方法是compare()方法,提供比较字符串的功能。具体流程如下:

  1. 比较时,会以较短的那个字符串长度为准比较,循环遍历两个字符串,比较是否每个字符都相同,如果是大小写不同会在转换大小写后再次比较。
  2. 如果确实出现有字不串不相同的,返回不同的字符两者之差(c1-c2),因为两者不是相同的字符串,返回的不可能是0,也就代表着两个字符串不相同。
  3. 如果比较完后发现全部字符相同,就返回两字符长度之差(n1-n2),如果两个字符串长度不同,那返回的结果仍然是一个非0的数字,代表两字符串不相同。
        public int compare(String s1, String s2) {
            int n1 = s1.length();
            int n2 = s2.length();
            int min = Math.min(n1, n2);
            for (int i = 0; i < min; i++) {//1
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1);
                    c2 = Character.toUpperCase(c2);
                    if (c1 != c2) {
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {
                            // No overflow because of numeric promotion
                            return c1 - c2;//2
                        }
                    }
                }
            }
            return n1 - n2;//3
        }
复制代码

构造方法

  String构造方法有很多,只挑选一部分分析。
  默认的无参构造函数和以String为参数的构造方法都是通过直接给成员变量赋值完成。

    public String() {
        this.value = "".value;
    }
    
        public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
复制代码

  如果传入的参数是一个字节数组,在赋值给value的时候不是直接赋值,而是重新复制一个数组,将新数组赋值给value,避免外部通过那个数组引用修改String内部的value。

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

  传入构造参数的参数重包含了编码格式,可以使用指定的编码格式来创建字符串。

    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, Charset charset) {
        if (charset == null)
            throw new NullPointerException("charset");
        checkBounds(bytes, offset, length);
        this.value =  StringCoding.decode(charset, bytes, offset, length);
    }
复制代码

  传入参数为StringBuffer和StringBuilder,这个个类型的实例实际上表示的是可变字符串,内部存储也是通过字节数组来存储字符串信息,所以是直接讲传入的可变字符串内部的字符数组复制后,赋值给给value。

    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 int length() {
        return value.length;
    }
复制代码

  判断字符串是否为空,实际上就是判断内部字符数组长度是否为0。

    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];
    }
复制代码

   返回指定索引处的码点(字符对应的int值),会先判断是否越界,然后调用Character类的方法得到对应的码点。

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

  将字符串中的一部分赋值给两一个字符数组,是通过将字符串内部的数组复制后再赋值给目的数组。

   void getChars(char dst[], int dstBegin) {
        System.arraycopy(value, 0, dst, dstBegin, value.length);
    }
复制代码

  获得字符串对应的字节数组,可以指定编码格式。

    public byte[] getBytes(String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null) throw new NullPointerException();
        return StringCoding.encode(charsetName, value, 0, value.length);
    }

    public byte[] getBytes(Charset charset) {
        if (charset == null) throw new NullPointerException();
        return StringCoding.encode(charset, value, 0, value.length);
    }

    public byte[] getBytes() {
        return StringCoding.encode(value, 0, value.length);
    }
复制代码

  通过字方法来判断两个字符串是否相等。具体流程如下:

  1. 判断应用是否相等,如果想等直接返回true。
  2. 判断要比较的对象是否是String类型的实例,如果是,继续进行比较。
  3. 判断待比较的字符串长度是否和本字符串一致,如果一直就继续比较。
  4. 循环遍历两个字符串的内部的字符数组的每一个字符是否相等(没有做大小写转换,大小写不同则视为不相同)。
    public boolean equals(Object anObject) {
        if (this == anObject) {//1
            return true;
        }
        if (anObject instanceof String) {//2
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {//3
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {//4
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
复制代码

  判断是否和其他字符序列内部的内容相同。其实主要是用来喝Stringbuffer和StringBuilder比较。具体流程如下:

  1. 判断传入的序列是否是AbstractBuilder类的子类的实例,如果是就继续判断。
  2. 判断是否是StringBuffer类的实例,若果是就通过synchronized加锁调用nonSyncContentEquals()方法保证线程安全(因为StringBuffer是线程安全的),如果不是那么就直接调用。nonSyncContentEquals()方法内部通过遍历内部的字符数组比较内容是否相等。如果都不是就继续进行判断。
  3. 判断是否是字符串,如果是就调用equals()方法比较是否相等。如果不是就继续进行判断。
  4. 走到这一步说明这是一个通用的CharSequeeu,判断这个序列是否和字符串长度相等。如果相等就进行遍历,比较内部字符数组的内容是否相等。
    public boolean contentEquals(CharSequence cs) {
        // Argument is a StringBuffer, StringBuilder
        if (cs instanceof AbstractStringBuilder) {//1
            if (cs instanceof StringBuffer) {//2
                synchronized(cs) {
                   return nonSyncContentEquals((AbstractStringBuilder)cs);
                }
            } else {
                return nonSyncContentEquals((AbstractStringBuilder)cs);
            }
        }
        // Argument is a String
        if (cs instanceof String) {//3
            return equals(cs);
        }
        // Argument is a generic CharSequence
        char v1[] = value;
        int n = v1.length;
        if (n != cs.length()) {//4
            return false;
        }
        for (int i = 0; i < n; i++) {
            if (v1[i] != cs.charAt(i)) {
                return false;
            }
        }
        return true;
    }
    
        private boolean nonSyncContentEquals(AbstractStringBuilder sb) {
        char v1[] = value;
        char v2[] = sb.getValue();
        int n = v1.length;
        if (n != sb.length()) {
            return false;
        }
        for (int i = 0; i < n; i++) {
            if (v1[i] != v2[i]) {
                return false;
            }
        }
        return true;
    }
复制代码

  忽略大小写,基佬两个字符串是否相等。先判断引用是否相等,相等直接返回true,如果不相等就判断带比较的字符串是否为空,长度是否和本字符串相等,并且调用regionMatches()方法是否返回为true,如果都满足返回true,表示相等。regionMatches()方法是忽略大小写的情况下比较两个字符串的子串是否相等。

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

  标胶两个字符串的的大小。具体流程如下:

  1. 获得两个字符串中较短的那个字符串的长度,赋值给lim。
  2. 循环遍历两个字符串内部的字节数组,直到遍历到第limci,比较字符是否相等,如果不相等就返回两字符之差(c1-c2),如果都相等就返回两字符串长度之差,只有两个字符串完全相等时,才会返回0.
    public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);//1
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {//2
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }
复制代码

  此方法时可以指定字符串前缀开始的偏移量,然后判断是偏移量开始的位置是否与前缀字符串相同。判断是否以某字符串为前缀和后缀都是基于此实现。具体流程如下:

  1. 判断偏移量是否合法如果不合法直接返回false。
  2. 循环遍历(从指定的偏移量开始),比较是否相等,如果都相等就返回true,否则还是返回false。
    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.
        if ((toffset < 0) || (toffset > value.length - pc)) {//1
            return false;
        }
        while (--pc >= 0) {//2
            if (ta[to++] != pa[po++]) {
                return false;
            }
        }
        return true;
    }

复制代码

  计算hash值。具体流程如下:

  1. 判断hash的值是否为0,如果为0,说明还没有初始化,同时判断value的长度是否大于零,如果不大于,说明次字符串时空字符串,默认hash值为0,不需要再做哈希运算。
  2. 遍历字符数组,运算公式是h = 31 * h + val[i],有两个好处:第一,字符串靠前的字符运算出来的hash值比重更大,如果是对字符串做hash运算,前缀相近的能更大几率哈希到比较近的位置,能够聚集前缀相似的数据(局部性原理);第二,选择的31是素数,能更好的离散数据(为什么是31而不是其他素数我也不太明白,不过31的二进制表示每一位都为1)。
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {//1
            char val[] = value;

            for (int i = 0; i < value.length; i++) {//2
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }
复制代码

转载于:https://juejin.im/post/5ce6601e6fb9a07edf271ce9

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值