Java 杂谈Java技术分享Java

String 源码浅析(一)

2018-12-30  本文已影响6人  张少林同学
image

前言

相信作为 JAVAER,平时编码时使用最多的必然是 String 字符串,而相信应该存在不少人对于 Stringapi 很熟悉了,但没有看过其源码实现,其实我个人觉得对于 api 的使用,最开始的阶段是看其官方文档,而随着开发经验的积累,应当尝试去看源码实现,这对自身能力的提升是至关重要的。当你理解了源码之后,后面对于 api 的使用也会更加得心应手!

备注:以下记录基于 jdk8 环境

String 只是一个类

String 其实只是一个类,我们大致可以从以下几个角度依次剖析它:

  1. 类继承关系
  2. 类成员变量
  3. 类构造方法
  4. 类成员方法
  5. 相关静态方法

继承关系

IDEA 自带插件导出 String 的 UML 类图如下:

image

从图中马上可以看出,String 实现了接口 SerializableComparableCharSequence,简单介绍一下这三个接口的作用:

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 有很多重载的构造方法,介绍如下:

  1. 空参数构造方法,初始化字符串实例,默认为空字符,理论上不需要用到这个构造方法,实际上定义一个空字符 String = "" 就会初始化一个空字符串的 String 对象,而此构造方法,也是把空字符的 value[] 拷贝一遍而已,源码实现如下:

      public String() {
         this.value = "".value;
     }
    
  2. 通过一个字符串参数构造 String 对象,实际上 将形参的 valuehash 赋值给实例对象作为初始化,相当于深拷贝了一个形参String对象,源码如下:

      public String(String original) {
            this.value = original.value;
            this.hash = original.hash;
        }
    
  3. 通过字符数组去构建一个新的String对象,这里使用 Arrays.copyOf 方法拷贝字符数组

     public String(char value[]) {
            this.value = Arrays.copyOf(value, value.length);
        }
    
  4. 在源字符数组基础上,通过偏移量(起始位置)和字符数量,截取构建一个新的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);
        }
    
  5. 在源整数数组的基础上,通过偏移量(起始位置)和字符数量,截取构建一个新的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;
    }
    
  6. 通过源字节数组,按照一定范围,从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);
    }
    
  7. 与第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);
    }
    
  8. 通过源字节数组,构造一个字符串实例,同时指定字符编码,具体实现其实是调用第6个构造器,起始位置为0,截取长度为字节数组长度

     public String(byte bytes[], String charsetName)
            throws UnsupportedEncodingException {
        this(bytes, 0, bytes.length, charsetName);
    }
    
  9. 通过源字节数组,构造一个字符串实例,同时指定字符编码,具体实现其实是调用第7个构造器,起始位置为0,截取长度为字节数组长度

     public String(byte bytes[], Charset charset) {
        this(bytes, 0, bytes.length, charset);
    }
    
  10. 通过源字节数组,按照一定范围,从offset开始截取length个长度,初始化 String 实例,与第六个构造器不同的是,使用系统默认字符编码

 public String(byte bytes[], int offset, int length) {
    //检查索引是否越界
    checkBounds(bytes, offset, length);
    //使用系统默认字符编码解码字节数组为字符数组
    this.value = StringCoding.decode(bytes, offset, length);
}
  1. 通过源字节数组,构造一个字符串实例,使用系统默认编码,具体实现其实是调用第10个构造器,起始位置为0,截取长度为字节数组长度

    public String(byte bytes[]) {
        this(bytes, 0, bytes.length);
    }
    
  2. StringBuffer 构建成一个新的String,比较特别的就是这个方法有synchronized锁 同一时间只允许一个线程对这个 buffer 构建成String对象,是线程安全的

     public String(StringBuffer buffer) {
        //对当前 StringBuffer 对象加同步锁
        synchronized(buffer) {
            //拷贝 StringBuffer 字符数组给当前实例的字符数组
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }
    
  3. StringBuilder 构建成一个新的String,与第12个构造器不同的是,此构造器不是线程安全的

     public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }
    

类成员方法

简单的总结

最后

由于篇幅原因,String 第一篇总结先到这里,后续部分将写另外写一遍记录,会第一时间推送公众号【张少林同学】,欢迎关注!

image
上一篇下一篇

猜你喜欢

热点阅读