前言

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

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

String 只是一个类

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

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

继承关系

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

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

  • Serializable :实现该接口的类将具备序列化的能力,该接口没有任何实现,仅仅是一直标识作用。
  • Comparable:实现此接口的类具备比较大小的能力,比如实现此接口的对象的列表(和数组)可以由 Collections 类的静态方法 sort 进行自动排序。
  • CharSequence:字符序列统一的我接口。提供字符序列通用的操作方法,通常是一些只读方法,许多字符相关的类都实现此接口,以达到对字符序列的操作,比如:StringStringBuffer 等。

String 类定义如下:

1public final class String
2    implements java.io.Serializable, Comparable<String>, CharSequence{
3        ...
4    }

final 修饰符可知, String 类是无法被继承,不可变类。

类成员变量

这里主要介绍最关键的一个成员变量 value[],其定义如下:

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

String 是一个字符串,由字符 char 所组成,因此实际上 String 内部其实就是一个字符数组,用 value[] 表示,注意这里的 value[] 是用 final 修饰的,表示该值是不允许修改的

类构造方法

String 有很多重载的构造方法,介绍如下:

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

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

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

    1 public String(char value[]) {
    2        this.value = Arrays.copyOf(value, value.length);
    3    }
  4. 在源字符数组基础上,通过偏移量(起始位置)和字符数量,截取构建一个新的String对象。

     1public String(char value[], int offset, int count) {
    2        //如果偏移量小于0,则抛出越界异常
    3        if (offset < 0) {
    4            throw new StringIndexOutOfBoundsException(offset);
    5        }
    6        if (count <= 0) {
    7            //如果字符数量小于0,则抛出越界异常
    8            if (count < 0) {
    9                throw new StringIndexOutOfBoundsException(count);
    10            }
    11            //在截取的字符数量为0的情况下,偏移量在字符串长度范围内,则返回空字符
    12            if (offset <= value.length) {
    13                this.value = "".value;
    14                return;
    15            }
    16        }
    17        // Note: offset or count might be near -1>>>1.
    18        //如果偏移量大于字符总长度-截取的字符长度,则抛出越界异常
    19        if (offset > value.length - count) {
    20            throw new StringIndexOutOfBoundsException(offset + count);
    21        }
    22        //使用Arrays.copyOfRange静态方法,截取一定范围的字符数组,从offset开始,长度为offset+count,赋值给当前实例的字符数组
    23        this.value = Arrays.copyOfRange(value, offset, offset+count);
    24    }
  5. 在源整数数组的基础上,通过偏移量(起始位置)和字符数量,截取构建一个新的String对象。这里的整数数组表示字符对应的ASCII整数值

     1    public String(int[] codePoints, int offset, int count) {
    2    //如果偏移量小于0,则抛出越界异常
    3    if (offset < 0) {
    4        throw new StringIndexOutOfBoundsException(offset);
    5    }
    6    if (count <= 0) {
    7        //如果字符数量小于0,则抛出越界异常
    8        if (count < 0) {
    9            throw new StringIndexOutOfBoundsException(count);
    10        }
    11        //在截取的字符数量为0的情况下,偏移量在字符串长度范围内,则返回空字符
    12        if (offset <= codePoints.length) {
    13            this.value = "".value;
    14            return;
    15        }
    16    }
    17    // Note: offset or count might be near -1>>>1.
    18    如果偏移量大于字符总长度-截取的字符长度,则抛出越界异常
    19    //if (offset > codePoints.length - count) {
    20        throw new StringIndexOutOfBoundsException(offset + count);
    21    }
    22    final int end = offset + count;
    23    // 这段逻辑是计算出字符数组的精确大小n,过滤掉一些不合法的int数据
    24    int n = count;
    25    for (int i = offset; i < end; i++) {
    26        int c = codePoints[i];
    27        if (Character.isBmpCodePoint(c))
    28            continue;
    29        else if (Character.isValidCodePoint(c))
    30            n++;
    31        else throw new IllegalArgumentException(Integer.toString(c));
    32    }
    33    // 按照上一步骤计算出来的数组大小初始化数组
    34    final char[] v = new char[n];
    35    //遍历填充字符数组
    36    for (int i = offset, j = 0; i < end; i++, j++) {
    37        int c = codePoints[i];
    38        if (Character.isBmpCodePoint(c))
    39            v[j] = (char)c;
    40        else
    41            Character.toSurrogates(c, v, j++);
    42    }
    43    //赋值给当前实例的字符数组
    44    this.value = v;
    45}
  6. 通过源字节数组,按照一定范围,从offset开始截取length个长度,初始化 String 实例,同时可以指定字符编码。

     1public String(byte bytes[], int offset, int length, String charsetName)
    2        throws UnsupportedEncodingException {
    3    //字符编码参数为空,抛出空指针异常
    4    if (charsetName == null)
    5        throw new NullPointerException("charsetName");
    6    //静态方法 检查字节数组的索引是否越界
    7    checkBounds(bytes, offset, length);
    8    //使用 StringCoding.decode 将字节数组按照一定范围解码为字符串,从offset开始截取length个长度
    9    this.value = StringCoding.decode(charsetName, bytes, offset, length);
    10}
  7. 与第6个构造相似,只是编码参数重载为 Charset 类型

    1  public String(byte bytes[], int offset, int length, Charset charset) {
    2    if (charset == null)
    3        throw new NullPointerException("charset");
    4    checkBounds(bytes, offset, length);
    5    this.value =  StringCoding.decode(charset, bytes, offset, length);
    6}
  8. 通过源字节数组,构造一个字符串实例,同时指定字符编码,具体实现其实是调用第6个构造器,起始位置为0,截取长度为字节数组长度

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

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

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

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

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

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

类成员方法

  • 获取字符串长度,实际上是获取字符数组长度

    1  public int length() {
    2    return value.length;
    3}
  • 判断字符串是否为空,实际上是盼复字符数组长度是否为0

    1public boolean isEmpty() {
    2    return value.length == 0;
    3}
  • 根据索引参数获取字符

    1 public char charAt(int index) {
    2    //索引小于0或者索引大于字符数组长度,则抛出越界异常
    3    if ((index < 0) || (index >= value.length)) {
    4        throw new StringIndexOutOfBoundsException(index);
    5    }
    6    //返回字符数组指定位置字符
    7    return value[index];
    8}
  • 根据索引参数获取指定字符ASSIC码(int类型)

    1  public int codePointAt(int index) {
    2    //索引小于0或者索引大于字符数组长度,则抛出越界异
    3    if ((index < 0) || (index >= value.length)) {
    4        throw new StringIndexOutOfBoundsException(index);
    5    }
    6    //返回索引位置指定字符ASSIC码(int类型)
    7    return Character.codePointAtImpl(value, index, value.length);
    8}
  • 返回index位置元素的前一个元素的ASSIC码(int型)

    1public int codePointBefore(int index) {
    2    //获得index前一个元素的索引位置
    3    int i = index - 1;
    4    //检查索引是否越界
    5    if ((i < 0) || (i >= value.length)) {
    6        throw new StringIndexOutOfBoundsException(index);
    7    }
    8    return Character.codePointBeforeImpl(value, index, 0);
    9}
  • 方法返回的是代码点个数,是实际上的字符个数,功能类似于length(),对于正常的String来说,length方法和codePointCount没有区别,都是返回字符个数。但当String是Unicode类型时则有区别了。例如:String str = “/uD835/uDD6B” (即使 'Z' ), length() = 2 ,codePointCount() = 1

    1 public int codePointCount(int beginIndex, int endIndex) {
    2    if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) {
    3        throw new IndexOutOfBoundsException();
    4    }
    5    return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
    6}
  • 也是相对Unicode字符集而言的,从index索引位置算起,偏移codePointOffset个位置,返回偏移后的位置是多少,例如,index = 2 ,codePointOffset = 3 ,maybe返回 5

    1public int offsetByCodePoints(int index, int codePointOffset) {
    2    if (index < 0 || index > value.length) {
    3        throw new IndexOutOfBoundsException();
    4    }
    5    return Character.offsetByCodePointsImpl(value, 0, value.length,
    6            index, codePointOffset);
    7}
  • 这是一个不对外的方法,是给String内部调用的,因为它是没有访问修饰符的,只允许同一包下的类访问 参数:dst[]是目标数组,dstBegin是目标数组的偏移量,既要复制过去的起始位置(从目标数组的什么位置覆盖) 作用就是将String的字符数组value整个复制到dst字符数组中,在dst数组的dstBegin位置开始拷贝

    1void getChars(char dst[], int dstBegin) {
    2    System.arraycopy(value, 0, dst, dstBegin, value.length);
    3}
  • 得到char字符数组,原理是getChars() 方法将一个字符串的字符复制到目标字符数组中。 参数:srcBegin是原始字符串的起始位置,srcEnd是原始字符串要复制的字符末尾的后一个位置(既复制区域不包括srcEnd) dst[]是目标字符数组,dstBegin是目标字符的复制偏移量,复制的字符从目标字符数组的dstBegin位置开始覆盖。

     1public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
    2    if (srcBegin < 0) {
    3        throw new StringIndexOutOfBoundsException(srcBegin);
    4    }
    5    if (srcEnd > value.length) {
    6        throw new StringIndexOutOfBoundsException(srcEnd);
    7    }
    8    if (srcBegin > srcEnd) {
    9        throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
    10    }
    11    System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    12}
  • 获取字符串的字节数组,按照指定字符编码将字符串解码为字节数组

    1public byte[] getBytes(String charsetName)
    2        throws UnsupportedEncodingException {
    3    if (charsetName == null) throw new NullPointerException();
    4    return StringCoding.encode(charsetName, value, 0, value.length);
    5}
  • 获取字符串的字节数组,按照指定字符编码将字符串解码为字节数组

    1public byte[] getBytes(Charset charset) {
    2    if (charset == null) throw new NullPointerException();
    3    return StringCoding.encode(charset, value, 0, value.length);
    4}
  • 获取字符串的字节数组,按照系统默认字符编码将字符串解码为字节数组

    1 public byte[] getBytes() {
    2    return StringCoding.encode(value, 0, value.length);
    3}

简单的总结

  • String 被 修饰符 final 修饰,是无法被继承的,不可变类
  • String 实现 Serializable 接口,可以被序列化
  • String 实现 Comparable 接口,可以用于比较大小
  • String 实现 CharSequence 接口,表示一直有序字符序列,实现了通用的字符序列方法
  • String 是一个字符序列,内部数据结构其实是一个字符数组,所有的操作方法都是围绕这个字符数组的操作。
  • String 中频繁使用到了 System 类的 arraycopy 方法,目的是拷贝字符数组

最后

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

String 源码浅析(一)的更多相关文章

  1. String 源码浅析————终结篇

    写在前面 说说这几天看源码的感受吧,其实 jdk 中的源码设计是最值得进阶学习的地方.我们在对 api 较为熟悉之后,完全可以去尝试阅读一些 jdk 源码,打开 jdk 源码后,如果你英文能力稍微过得 ...

  2. String源码浅析

    如果问你,开发过程中用的最多的类是哪个?你可能回答是HashMap,一个原因就是HashMap的使用量的确很多,还有就是HashMap的内容在面试中经常被问起. 但是在开发过程中使用最多的类其实并不是 ...

  3. 我对java String的理解 及 源码浅析

    摘要: 摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢! 每天起床告诉自己,自己的目标是 ”技术 + 英语 还有生活“! ...

  4. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  5. 【深入浅出jQuery】源码浅析2--奇技淫巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  6. HashSet其实就那么一回事儿之源码浅析

    上篇文章<HashMap其实就那么一回事儿之源码浅析>介绍了hashMap,  本次将带大家看看HashSet, HashSet其实就是基于HashMap实现, 因此,熟悉了HashMap ...

  7. 【深入浅出jQuery】源码浅析2--使用技巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  8. spring源码浅析——IOC

    =========================================== 原文链接: spring源码浅析--IOC   转载请注明出处! ======================= ...

  9. java并发:jdk1.8中ConcurrentHashMap源码浅析

    ConcurrentHashMap是线程安全的.可以在多线程中对ConcurrentHashMap进行操作. 在jdk1.7中,使用的是锁分段技术Segment.数据结构是数组+链表. 对比jdk1. ...

随机推荐

  1. [hdu4347]The Closest M Points(平衡树式kdtree)

    解题关键:模板题(结合起来了) #include<iostream> #include<cstdio> #include<cstring> #include< ...

  2. Spring Data JPA 参考指南 中文版

    附下载地址:https://www.gitbook.com/book/ityouknow/spring-data-jpa-reference-documentation/details

  3. 嵌套列表的加权和 · Nested List Weight Sum

    [抄题]: Given a nested list of integers, return the sum of all integers in the list weighted by their ...

  4. Python3 模块与包

    一.模块介绍 什么是模块? 常见的场景:一个模块就是一个包含了一组功能的Python文件,比如spam.py,模块名为spam,可以通过import spam使用. 在Python中,模块的使用方式都 ...

  5. css常用技巧:input提示文字;placeholder字体修改

    1 很多网站都需要更改 <input>内部的placeholder 文字颜色属性:下面来介绍下这个技巧. 2  源代码: <!DOCTYPE html><html> ...

  6. JAVA中简单的MD5加密类(MD5Utils)

    MD5加密分析:   JDK API:   获取对象的API:   加密的API:   package cn.utils; import java.security.MessageDigest; im ...

  7. xfce4快捷键设置

    xfce4的"Keyboard"可以方便的设置启动应用程序的快捷键. 例如,添加xfce4-terminal和emacs的启动快捷键 Alt+F3打开"Applicati ...

  8. Arch Linux freemind中文乱码

    原因:jre没有可用的中文字体 解决方法: (1) 安装中文字体,例如文泉驿微黑 pacman -S wqy-microhei (2) jre字体目录下建立fallback,并链接中文字体作为后备字体 ...

  9. 编写高质量代码改善C#程序的157个建议——建议72:在线程同步中使用信号量

    建议72:在线程同步中使用信号量 所谓线程同步,就是多个线程在某个对象上执行等待(也可理解为锁定该对象),直到该对象被解除锁定.C#中对象的类型分为引用类型和值类型.CLR在这两种类型上的等待是不一样 ...

  10. Android-获取网络图片设置壁纸

    下载图片,设置壁纸 的代码: package liudeli.async; import android.app.Activity; import android.app.ProgressDialog ...