JDK源码——String(一)


通读源码

1.类的定义

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
  • final class
  • 实现了Serializable 可序列化
  • 实现了Comparable 可比较
  • 实现了CharSequence 表明可读连续字符的特性

2.字段

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

存储字符的字符数组,也是final

/** Cache the hash code for the string */
private int hash; // Default to 0

缓存字符串的Hash值,默认为0

/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;

序列化的id值

private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];

用于指定哪些字段需要被序列化

3.构造方法

public String() {
        this.value = "".value;
}

空值构造,空值为 “”,为拷贝构造

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

传入一个String,同样是拷贝构造,同时拷贝了字符数组和hash值

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

通过传入的字符数组的子串构造,同时传入起始的index和要复制的长度

  • 检查offset的坐标是否合法,不合法抛出所以超出界限的异常,保障offset >= 0
  • 检查count,小于0直接抛异常;等于0且offset在合法区间构造空值
  • 注意这个Note,提示我们这两个值可能会接近Integer.MAX_VALUE(1的补码表示为00000000 00000000 00000000 00000001,-1则是1取反+1为11111111 11111111 11111111 11111111,>>>为无符号右移,高位补0,变成01111111 11111111 11111111 11111111,符号位为0,此数为最大的整数)
  • 这里提示offset和count都有可能取最大整数,故而下面的判断需要写成 offset > value.length - count而不是offset + count > value.length,因为后者会可能导致整数溢出
  • 这个判断同样实在检查合理性,是否存在offset和count选取的部分超出字符数组的情况
  • 最后检查完毕,直接复制
public String(int[] codePoints, int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= codePoints.length) {
                this.value = "".value;
                return;
            }
        }
        // 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;
    }

​ 通过codePoints来构造字符串,codePoints指的是Unicode为世界上每一个字符赋予的一个编号,而这个编号编码成二进制的不同方式也就区分了像UTF8,UTF16,UTF32这样的编码集

​ 除开上面讲到的判断,第一步,就是计算最终将那些BMP规范之外java字符之内的字符算进来之后的字符数组的总长度,第二步,就是区别的对待上述分出的这两部分字符,BMP范围内的直接强转,之外的通过特殊的方法赋值

private static void checkBounds(byte[] bytes, int offset, int length) {
        if (length < 0)
            throw new StringIndexOutOfBoundsException(length);
        if (offset < 0)
            throw new StringIndexOutOfBoundsException(offset);
        if (offset > bytes.length - length)
            throw new StringIndexOutOfBoundsException(offset + length);
}

补充一个工具方法,用于判断字节数组的offset 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);
    }

通过传入的字符集名称来队传入的字节数组进行编码,构造成一个字符串

先检查参数的合法性,首先字符集名称不能为空,然后是字节数组的两个参数不能越界,最后通过decode方法进行编码

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

此方法同上,只不过用来表示字符集的方式是通过Charset类而不是字符集的名字,有对应的decode方法处理

public String(byte bytes[], String charsetName)
            throws UnsupportedEncodingException {
        this(bytes, 0, bytes.length, charsetName);
}

此方法同上,只不过是不选定返回,通过整个字节数组构造

public String(byte bytes[], Charset charset) {
        this(bytes, 0, bytes.length, charset);
}

同理

 public String(byte bytes[], int offset, int length) {
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(bytes, offset, length);
 }

使用默认字符集,转换部分

public String(byte bytes[]) {
        this(bytes, 0, bytes.length);
}

使用默认字符集,转换全部

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

通过StringBuffer和StringBuilder构造字符串,通过这里可以看出,StringBuffer的toString方法是线程安全的,也正因为如此才会比StringBuilder的toString要慢一点,另一个刚好相反

    String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;
    }

这是一个protected修饰的方法,比String(char[])方法多了一个share,并由注释可以知道share为false不被支持,且在方法里面share参数没有被使用,故而可以知道这个参数的作用是为了和String(char[])方法做区分

我们回到方法实现,发现和没有share的方法的区别很明显,此方法是直接将传入的value的引用给当前的String的value,而没有share的方法则是通过Arrays.copyOf的方式进行拷贝

为什么会有这样一个方法的存在?

分析这个方法比之String(char[]) 方法的优点,主要体现在两个方面:

  • 一个是占用的内存空间更少,因为这样构造的String的字符数组同传进来的数组共享同一片空间;
  • 二个是直接赋值的方式要比一个个copy更快

而这个方法带来的影响也是很显然,这样的赋值方式其实有破坏String的不可变性的可能,至于不可变性的重要程度后续会谈,通过直接赋值的方式,如果传入的value变动,那么String就也会变,因此这个方法才不是public,在我们后续阅读的String部分方法中也会发现它的身影,到时候我们也会细谈

3.普通方法

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

返回字符串长度

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

获取字符串指定位置的字符,先判断index的合法性,合法返回

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

获取指定索引的代码点,使用Character的独特方法获取

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

获取前一个代码点

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

获取给定索引的偏移量下的代码点

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

将字符串复制给从dstBegin处开始的dst字符数组的区域,没有边界检查

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
    if (srcBegin < 0) {
        throw new StringIndexOutOfBoundsException(srcBegin);
    }
    if (srcEnd > value.length) {
        throw new StringIndexOutOfBoundsException(srcEnd);
    }
    if (srcBegin > srcEnd) {
        throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
    }
    System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

前一个方法的带边界检查的版本,从srcBegin到srcEnd -1 处的字符复制到dst从dstBegin开始的区域

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

上述方法使用默认字符集

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

重点方法

首先,我们将自己同传入对象的引用进行比较,如果是同一块内存空间,返回相同,此时我们保证两者不是同一个对象

然后,判断传进来的对象是否是String(因为String不可以被继承),如果是继续后续判断,如果不是直接返回false,此时我们保证传入的对象是String这个类

之后,我们将传进来的对象显示转换为String

接下来的比较逻辑分为几步:

  • 比较长度,相等就继续比较,不等直接返回false
  • 然后获取两个string的字符数组,从末尾到开头逐一比较,全相同才是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;
}

这又是一个内部使用的方法,看代码就是一段简单的逐字符比较,逻辑同equals后半段相同,传入的对象要么是StringBuilder要么是StringBuffer(原因下一个会讲),且是非线程安全的

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

见名知意,用于比较内容是否相等

首先为什么参数是CharSequence?

如果只有String这一个对象,上述的equals方法足够,因此方法主要是要考虑String与其他字符性质对象的内容比较

继续看之前,我们需要了解一下这几个类或者接口间关系

String,StringBuffer,StringBuilder都实现了CharSequence,然后AbstractStringBuilder有且仅有两个子类就是StringBuilder和StringBuffer

看代码

首先看是不是AbstractStringBuilder这一脉,实际上潜台词是是不是StringBuilder和StringBuffer之一(这俩也是final修饰的)

如果是,再看是不是StringBuffer,是就需要保证线程安全的比较,不是的话就只有可能是StringBuilder,就可以不用保证线程安全

如果不是,那再看是不是一个String,是的话就直接使用equals比较,

然后代码到这里,就是与非图中这三个的其他实现了CharSequence的类比较,比较思路同equals后半段一样

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

同样的见名知意,忽略大小写的比较,中i但看是怎么实现的,首先判断二者是不是同一个对象,是直接返回true,不是则继续比较,然后只要传进来的String为空或者二者长度不同都直接返回的是false,在前者保证的前提下,我们调用regionMathes方法比较,这个方法的比较过程,我们后续谈到该方法时再看。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值