写点什么

String 源码解读(JDK1.8)

作者:旅人与风
  • 2022 年 6 月 06 日
  • 本文字数:3922 字

    阅读完需:约 13 分钟

String源码解读(JDK1.8)

1. String 类关系

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

String 类被 final 关键字修饰,不能被继承,赋值后不可修改。

1.2 实现接口
  • java.io.Serializable:详解

  • 序列化接口,标记接口(Marker Interface),实现了标记接口的类仅仅是给自身贴了个”标记“,未实现该接口无法被序列化。

  • Comparable<String>

  • 比较器,值提供一个 compareTo 方法,用于比较两个字符串大小,下面会讲解该方法。

  • CharSequence

  • CharSequence 是字符值的可读序列。此接口提供对多种不同类型的字符序列的统一只读访问。

2. 成员变量

查看 String 源码我们可以看到,String 内部存储实际为 char 数组


public final class String implements java.io.Serializable, Comparable<String>, CharSequence {  /** 用于存储字符串的值 */  private final byte[] value;  /** 字符串的HashCode */  private int hash; // Default to 0}
复制代码

3. 构造方法

String 字符串有以下四个常用的构造方法:


// String 为参数的构造方法public String(String original) {    this.value = original.value;    this.hash = original.hash;}// char[] 为参数构造方法public String(char value[]) {    this.value = Arrays.copyOf(value, value.length);}// StringBuffer 为参数的构造方法public String(StringBuffer buffer) {    synchronized(buffer) {        this.value = Arrays.copyOf(buffer.getValue(), buffer.length());    }}// StringBuilder 为参数的构造方法public String(StringBuilder builder) {    this.value = Arrays.copyOf(builder.getValue(), builder.length());}
复制代码

4. equals、compareTo 比较字符串

4.1 equals()方法比较字符串

String 重写了 Object 中的 equals()方法


  1. 引用是否相同。

  2. 判断变量是否为 String。

  3. 比较数组长度。

  4. 循环遍历数组中每个字符。


public boolean equals(Object anObject) {    // 对象引用相同直接返回 true    if (this == anObject) {        return true;    }    // 判断需要对比的值是否为 String 类型,如果不是则直接返回 false    if (anObject instanceof String) {        String anotherString = (String)anObject;        int n = value.length;        if (n == anotherString.value.length) {            // 把两个字符串都转换为 char 数组对比            char v1[] = value;            char v2[] = anotherString.value;            int i = 0;            // 循环比对两个字符串的每一个字符            while (n-- != 0) {                // 如果其中有一个字符不相等就 true false,否则继续对比                if (v1[i] != v2[i])                    return false;                i++;            }            return true;        }    }    return false;}
复制代码


equalsIgnoreCase:忽略字符串的大小写进行字符串对比

4.2 compareTo()方法比较字符串

compareTo() 方法用于比较两个字符串,返回的结果为 int 类型的值,源码如下:


public int compareTo(String anotherString) {    int len1 = value.length;    int len2 = anotherString.value.length;    // 获取到两个字符串长度最短的那个 int 值    int lim = Math.min(len1, len2);    char v1[] = value;    char v2[] = anotherString.value;    int k = 0;    // 对比每一个字符    while (k < lim) {        char c1 = v1[k];        char c2 = v2[k];        if (c1 != c2) {            // 有字符不相等就返回差值            return c1 - c2;        }        k++;    }    // 返回长度的差值    return len1 - len2;}
复制代码


从源码中可以看出,compareTo() 方法会循环对比所有的字符,当两个字符串中有任意一个字符不相同时,则 return char1-char2。


两个方法都是用于比较字符串的,他们有一下不同:

  • equals() 可以接收一个 Object 类型的参数,而 compareTo() 只能接收一个 String 类型的参数;

  • equals() 返回值为 Boolean,而 compareTo() 的返回值则为 int。

  • 当 equals() 方法返回 true 时,或者 compareTo() 方法返回 0 时,则表示两个字符串相同。

5. substring、concat、replace 字符串处理

5.1 substring()切割字符串
public String substring(int beginIndex, int endIndex) {  // 开始小于0,结束大于字符串长度,结束小于开始均抛出StringIndexOutOfBoundsException  if (beginIndex < 0) {    throw new StringIndexOutOfBoundsException(beginIndex);  }  if (endIndex > value.length) {    throw new StringIndexOutOfBoundsException(endIndex);  }  int subLen = endIndex - beginIndex;  if (subLen < 0) {    throw new StringIndexOutOfBoundsException(subLen);  }  // 0到value.length返回自身,否则新建一个String返回  return ((beginIndex == 0) && (endIndex == value.length)) ? this    : new String(value, beginIndex, subLen);}
复制代码
5.2 concat()连接字符串
public String concat(String str) {  int otherLen = str.length();  if (otherLen == 0) {    return this;  }  int len = value.length;  // 新建一个字符数组长度为两个字符串的长度  char buf[] = Arrays.copyOf(value, len + otherLen);  // 值复制  str.getChars(buf, len);  // 新建String  return new String(buf, true);}
复制代码
5.3 replace()替换字符
public String replace(char oldChar, char newChar) {  if (oldChar != newChar) {    int len = value.length;    int i = -1;    char[] val = value; /* avoid getfield opcode */    // 找到第一个oldChar    while (++i < len) {      if (val[i] == oldChar) {        break;      }    }    if (i < len) {      char buf[] = new char[len];      for (int j = 0; j < i; j++) {        buf[j] = val[j];      }      while (i < len) {        char c = val[i];        // 挨个替换oldChar        buf[i] = (c == oldChar) ? newChar : c;        i++;      }      // 新建String      return new String(buf, true);    }  }  return this;}
复制代码


从这三个字符串处理方法可以看出,三个方法操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。所以进行这些操作后,最原始的字符串并没有被改变。

6. 其他重要方法

indexOf():查询字符串首次出现的下标位置 lastIndexOf():查询字符串最后出现的下标位置 contains():查询字符串中是否包含另一个字符串 toLowerCase():把字符串全部转换成小写 toUpperCase():把字符串全部转换成大写 length():查询字符串的长度 trim():去掉字符串首尾空格 split():把字符串分割并返回字符串数组 join():把字符串数组转为字符串

7. 关联问题

7.1 String 为什么要用 final 修饰?

final:安全,高效


将 String 设计成不可变的,将不会出现其内部值发生改变而造成的严重系统问题,在日常使用中一个常见场景如 dto 类,用作数据传输其内部值就应该置为 final 不可变,已确保上层调用的安全性。


高效可以用 JVM 中的字符串常量池来举例,字符串常量池为我们缓存了字符串,可以提高系统运行的效率,如果创建两个对象 s1,s2 都等于字符串”Java“,其在常量池中只存在一个字符串 Java,只是有两个引用指向它。

7.2 == 和 equals 的区别是什么?

== 对于基本数据类型来说,是用于比较 “值”是否相等的;而对于引用类型来说,是用于比较引用地址是否相同的。


Object 的 equals 就是==,而 String 重写了 equals,用于比较字符串的值。

7.3 String 和 StringBuilder、StringBuffer 有什么区别?

因为 String 类型是不可变的,所以在字符串拼接的时候如果使用 String 的话性能会很低,因此我们就需要使用另一个数据类型 StringBuffer,它提供了 append 和 insert 方法可用于字符串的拼接,它使用 synchronized 来保证线程安全,如下源码所示:


@Overridepublic synchronized StringBuffer append(Object obj) {    toStringCache = null;    super.append(String.valueOf(obj));    return this;}
@Overridepublic synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this;}
复制代码


因为它使用了 synchronized 来保证线程安全,所以性能不是很高,于是在 JDK 1.5 就有了 StringBuilder,它同样提供了 append 和 insert 的拼接方法,但它没有使用 synchronized 来修饰,因此在性能上要优于 StringBuffer,所以在非并发操作的环境下可使用 StringBuilder 来进行字符串拼接。

7.4 String 的 intern() 方法有什么含义?

new String 都是在堆上创建字符串对象。


当调用 intern() 方法时,如果常量池中存在该值直接返回,否则编译器会将字符串添加到常量池中(stringTable 维护),并返回指向该常量的引用。


通过字面量赋值创建字符串(如:String str=”twm”)时,会先在常量池中查找是否存在相同的字符串,若存在,则将栈中的引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。

7.5 String 类型在 JVM 中是如何存储的?

String 常见的创建方式有两种,new String() 的方式和直接赋值的方式


  1. 直接赋值:先去字符串常连池中查找是否已经有此值,如果有则会把引用地址直接指向此值,否则会在常连池中创建,然后再把引用指向此值。

  2. new String("abc"):首先"abc"在构造之前是一个常量池中的字符串,然后会在堆上创建一个字符串对象,将内存中的对象引用返回。

发布于: 刚刚阅读数: 4
用户头像

旅人与风

关注

还未添加个人签名 2019.09.18 加入

还未添加个人简介

评论

发布
暂无评论
String源码解读(JDK1.8)_Java_旅人与风_InfoQ写作社区