前言相信作为 JAVAER,平时编码时使用最多的必然是 String 字符串,而相信应该存在不少人对于 String 的 api 很熟悉了,但没有看过其源码实现,其实我个人觉得对于 api 的使用,最开始的阶段是看其官方文档,而随着开发经验的积累,应当尝试去看源码实现,这对自身能力的提升是至关重要的。当你理解了源码之后,后面对于 api 的使用也会更加得心应手!备注:以下记录基于 jdk8 环境String 只是一个类String 其实只是一个类,我们大致可以从以下几个角度依次剖析它:类继承关系类成员变量类构造方法类成员方法相关静态方法继承关系从 IDEA 自带插件导出 String 的 UML 类图如下:从图中马上可以看出,String 实现了接口 Serializable,Comparable,CharSequence,简单介绍一下这三个接口的作用:Serializable :实现该接口的类将具备序列化的能力,该接口没有任何实现,仅仅是一直标识作用。Comparable:实现此接口的类具备比较大小的能力,比如实现此接口的对象的列表(和数组)可以由 Collections 类的静态方法 sort 进行自动排序。CharSequence:字符序列统一的我接口。提供字符序列通用的操作方法,通常是一些只读方法,许多字符相关的类都实现此接口,以达到对字符序列的操作,比如:String,StringBuffer 等。String 类定义如下:public final class String implements java.io.Serializable, Comparable<String>, CharSequence{ … }由 final 修饰符可知, String 类是无法被继承,不可变类。类成员变量这里主要介绍最关键的一个成员变量 value[],其定义如下: /** The value is used for character storage. */ private final char value[];String 是一个字符串,由字符 char 所组成,因此实际上 String 内部其实就是一个字符数组,用 value[] 表示,注意这里的 value[] 是用 final 修饰的,表示该值是不允许修改的。类构造方法String 有很多重载的构造方法,介绍如下:空参数构造方法,初始化字符串实例,默认为空字符,理论上不需要用到这个构造方法,实际上定义一个空字符 String = "" 就会初始化一个空字符串的 String 对象,而此构造方法,也是把空字符的 value[] 拷贝一遍而已,源码实现如下: public String() { this.value = “".value; }通过一个字符串参数构造 String 对象,实际上 将形参的 value 和 hash 赋值给实例对象作为初始化,相当于深拷贝了一个形参String对象,源码如下: public String(String original) { this.value = original.value; this.hash = original.hash; }通过字符数组去构建一个新的String对象,这里使用 Arrays.copyOf 方法拷贝字符数组 public String(char value[]) { this.value = Arrays.copyOf(value, value.length); }在源字符数组基础上,通过偏移量(起始位置)和字符数量,截取构建一个新的String对象。public String(char value[], int offset, int count) { //如果偏移量小于0,则抛出越界异常 if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count <= 0) { //如果字符数量小于0,则抛出越界异常 if (count < 0) { throw new StringIndexOutOfBoundsException(count); } //在截取的字符数量为0的情况下,偏移量在字符串长度范围内,则返回空字符 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); } //使用Arrays.copyOfRange静态方法,截取一定范围的字符数组,从offset开始,长度为offset+count,赋值给当前实例的字符数组 this.value = Arrays.copyOfRange(value, offset, offset+count); }在源整数数组的基础上,通过偏移量(起始位置)和字符数量,截取构建一个新的String对象。这里的整数数组表示字符对应的ASCII整数值 public String(int[] codePoints, int offset, int count) { //如果偏移量小于0,则抛出越界异常 if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count <= 0) { //如果字符数量小于0,则抛出越界异常 if (count < 0) { throw new StringIndexOutOfBoundsException(count); } //在截取的字符数量为0的情况下,偏移量在字符串长度范围内,则返回空字符 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; // 这段逻辑是计算出字符数组的精确大小n,过滤掉一些不合法的int数据 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)); } // 按照上一步骤计算出来的数组大小初始化数组 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;}通过源字节数组,按照一定范围,从offset开始截取length个长度,初始化 String 实例,同时可以指定字符编码。public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException { //字符编码参数为空,抛出空指针异常 if (charsetName == null) throw new NullPointerException(“charsetName”); //静态方法 检查字节数组的索引是否越界 checkBounds(bytes, offset, length); //使用 StringCoding.decode 将字节数组按照一定范围解码为字符串,从offset开始截取length个长度 this.value = StringCoding.decode(charsetName, bytes, offset, length);}与第6个构造相似,只是编码参数重载为 Charset 类型 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);}通过源字节数组,构造一个字符串实例,同时指定字符编码,具体实现其实是调用第6个构造器,起始位置为0,截取长度为字节数组长度 public String(byte bytes[], String charsetName) throws UnsupportedEncodingException { this(bytes, 0, bytes.length, charsetName);}通过源字节数组,构造一个字符串实例,同时指定字符编码,具体实现其实是调用第7个构造器,起始位置为0,截取长度为字节数组长度 public String(byte bytes[], Charset charset) { this(bytes, 0, bytes.length, charset);}通过源字节数组,按照一定范围,从offset开始截取length个长度,初始化 String 实例,与第六个构造器不同的是,使用系统默认字符编码 public String(byte bytes[], int offset, int length) { //检查索引是否越界 checkBounds(bytes, offset, length); //使用系统默认字符编码解码字节数组为字符数组 this.value = StringCoding.decode(bytes, offset, length);}通过源字节数组,构造一个字符串实例,使用系统默认编码,具体实现其实是调用第10个构造器,起始位置为0,截取长度为字节数组长度public String(byte bytes[]) { this(bytes, 0, bytes.length);}将 StringBuffer 构建成一个新的String,比较特别的就是这个方法有synchronized锁 同一时间只允许一个线程对这个 buffer 构建成String对象,是线程安全的 public String(StringBuffer buffer) { //对当前 StringBuffer 对象加同步锁 synchronized(buffer) { //拷贝 StringBuffer 字符数组给当前实例的字符数组 this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); }}将 StringBuilder 构建成一个新的String,与第12个构造器不同的是,此构造器不是线程安全的 public String(StringBuilder builder) { this.value = Arrays.copyOf(builder.getValue(), builder.length());}类成员方法获取字符串长度,实际上是获取字符数组长度 public int length() { return value.length;}判断字符串是否为空,实际上是盼复字符数组长度是否为0public boolean isEmpty() { return value.length == 0;}根据索引参数获取字符 public char charAt(int index) { //索引小于0或者索引大于字符数组长度,则抛出越界异常 if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } //返回字符数组指定位置字符 return value[index];}根据索引参数获取指定字符ASSIC码(int类型) public int codePointAt(int index) { //索引小于0或者索引大于字符数组长度,则抛出越界异 if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } //返回索引位置指定字符ASSIC码(int类型) return Character.codePointAtImpl(value, index, value.length);}返回index位置元素的前一个元素的ASSIC码(int型)public int codePointBefore(int index) { //获得index前一个元素的索引位置 int i = index - 1; //检查索引是否越界 if ((i < 0) || (i >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return Character.codePointBeforeImpl(value, index, 0);}方法返回的是代码点个数,是实际上的字符个数,功能类似于length(),对于正常的String来说,length方法和codePointCount没有区别,都是返回字符个数。但当String是Unicode类型时则有区别了。例如:String str = “/uD835/uDD6B” (即使 ‘Z’ ), length() = 2 ,codePointCount() = 1 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);}也是相对Unicode字符集而言的,从index索引位置算起,偏移codePointOffset个位置,返回偏移后的位置是多少,例如,index = 2 ,codePointOffset = 3 ,maybe返回 5public int offsetByCodePoints(int index, int codePointOffset) { if (index < 0 || index > value.length) { throw new IndexOutOfBoundsException(); } return Character.offsetByCodePointsImpl(value, 0, value.length, index, codePointOffset);}这是一个不对外的方法,是给String内部调用的,因为它是没有访问修饰符的,只允许同一包下的类访问 参数:dst[]是目标数组,dstBegin是目标数组的偏移量,既要复制过去的起始位置(从目标数组的什么位置覆盖) 作用就是将String的字符数组value整个复制到dst字符数组中,在dst数组的dstBegin位置开始拷贝void getChars(char dst[], int dstBegin) { System.arraycopy(value, 0, dst, dstBegin, value.length);}得到char字符数组,原理是getChars() 方法将一个字符串的字符复制到目标字符数组中。 参数:srcBegin是原始字符串的起始位置,srcEnd是原始字符串要复制的字符末尾的后一个位置(既复制区域不包括srcEnd) dst[]是目标字符数组,dstBegin是目标字符的复制偏移量,复制的字符从目标字符数组的dstBegin位置开始覆盖。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);}获取字符串的字节数组,按照指定字符编码将字符串解码为字节数组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);}简单的总结String 被 修饰符 final 修饰,是无法被继承的,不可变类String 实现 Serializable 接口,可以被序列化String 实现 Comparable 接口,可以用于比较大小String 实现 CharSequence 接口,表示一直有序字符序列,实现了通用的字符序列方法String 是一个字符序列,内部数据结构其实是一个字符数组,所有的操作方法都是围绕这个字符数组的操作。String 中频繁使用到了 System 类的 arraycopy 方法,目的是拷贝字符数组最后由于篇幅原因,String 第一篇总结先到这里,后续部分将写另外写一遍记录,会第一时间推送公众号【张少林同学】,欢迎关注!