String源码浅谈

String源码浅谈

String对于我们都很熟悉,平时用的也不少,但是小伙伴们对于String的源码究竟了解多少?如果你真的了解String的源码,会避免很多坑,那我们开始吧。

String类

1
public final class String implements java.io.Serializable, Comparable<String>, CharSequence

String类的定义中,我们可以认识到

  • String类被final修饰,表示该类不能被别的类继承。

  • String类实现了java.io.Serializable接口,表示String类可以序列化。

    Serializable是一个空接口,它的实现类无需重写任何方法,只要实现了该接口,即相当于开启可序列化操作标识。

  • String类实现了Comparable<String>,字符串可以比较大小。

    String类通过重写Comparable接口的compareTo(T o)方法,实现字符串比较的逻辑。

  • String类实现了 CharSequence接口,表示是一个字符的序列,因为String的本质是一个char类型数组。

    java.lang.CharSequence接口是一个可读的字符序列。此接口为不同类型的字符序列提供了一种统一且只读的访问方式。在接口中定义的方法包括charAt(int index)length()toString()等方法。

字段属性

1
2
3
4
5
6
7
8
9
/** The value is used for character storage.*/
@Stable
private final byte[] value;

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

/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
  • byte[] value 数组是String类底层的数据结构,即String类是存储在value数组里面。

    需要注意的是该数组被final修饰,对于final修饰的数组,我们可以修改该数组的对象的值,但是不能改变指向的对象即不能指向别的数组。

    测试代码

1
2
3
4
5
6
7
8
9
public class ArrayTest {
public static void main(String[] args) {
final char[] chars = new char[]{'J','a','v','a'};
System.out.println(String.valueOf(chars));
chars[0] = 'j';
System.out.println(String.valueOf(chars));
//chars = new char[]{'G','o'};
}
}

结果

1
2
Java
java

需要注意,在测试代码里面,即代码第七行,指向别的数组对象,编译直接报错。

  • int hash用来保存某一个String实例的哈希值,可以说是哈希值的一个缓存。

    例如,当String放入HashMap中,作为key来使用。每次插入一个键值対时,不需要重新计算key的哈希值,直接取出key的缓存hash值即可,在一定程度上,加快了HashMap的效率。

  • long serialVersionUID 用于保证版本一致性。

    String实现了Serializable接口,因此需要拥有一个序列化的ID。序列化时,将此ID与对象一并写入到文件中,反序列化时,检测该类中的ID与文件中的ID是否一致,一致的话,说明版本一致,序列化成功。

需要注意,当实现Serializable接口,如果不声明long serialVersionUID的值,也可以进行序列化和反序列化。因为Java会帮助我们自动生成一个long serialVersionUID的值。

但是需要注意的是,如果我们修改实现Serializable接口的类时,Java自动生成的long serialVersionUID的值也会发生改变。如果我们需要保证程序向前兼容,此时会报错,因为该值发生了改变。即我们本地序列化的serialVersionUID的值和我们类里的serialVersionUID的值不一致,无法序列化,报错。如果我们自己声明该值,并且保证该值不变,相同情况下,可以顺利序列化,保证程序的向前兼容性。

想要深入了解,可以参考文献的第一篇文章。

构造方法

  • 无参构造方法

创建空的字符串,因为String是不可变的,所以该构造方法使用较少。

1
2
3
4
5
6
7
8
9
/**
* Initializes a newly created {@code String} object so that it represents
* an empty character sequence. Note that use of this constructor is
* unnecessary since Strings are immutable.
*/
public String() {
this.value = "".value;
this.coder = "".coder;
}
  • 接收String类型参数的构造函数
1
2
3
4
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
  • 接收char[] 数组类型参数的构造函数
1
2
3
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
1
2
3
4
5
6
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}

根据代码块我们可以发现构造函数调用了Arraycopyof方法,而copyof方法基于System.arraycopy方法。System.arraycopy方法是native方法,由JVM调用非Java方法实现。

  • 通过解码指定字节数组,生成字符串。
1
2
3
4
5
6
7
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);
}

常用方法

  • 字符串本身的属性
1
2
3
public int length() {
return value.length;
}

String底层实现方式是value数组,即数组的长度。

1
2
3
public boolean isEmpty() {
return value.length == 0;
}

String底层实现方式是value数组,字符串为空,即数组的长度为零。

  • 字符串之间的比较
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 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重写了Object类的equals方法,如果是同一个对象直接返回。否则逐一比较两个字符串的每个char元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
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;
}

String实现了Comparable接口的compareTo方法,从起始位置开始,比较char元素的大小。

  • 字符串处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public String substring(int beginIndex, int endIndex) {
    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);
    }
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
    : new String(value, beginIndex, subLen);
    }

    Stringsubstring方法用于截断字符串,返回字符串的一部分。我们可以发现返回的是一个新的字符串,而不是在之前的字符串上面修改,因为之前的字符串是不可变的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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);
    return new String(buf, true);
    }

    Stringconcat方法用于拼接字符串,将str拼接到现有的字符串上,返回拼接好的字符串。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public String replace(char oldChar, char newChar) {
    if (oldChar != newChar) {
    int len = value.length;
    int i = -1;
    char[] val = value; /* avoid getfield opcode */

    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];
    buf[i] = (c == oldChar) ? newChar : c;
    i++;
    }
    return new String(buf, true);
    }
    }
    return this;
    }

    Stringreplace方法用于将字符串的oldChar替换成newChar,返回新的字符串。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    public String[] split(String regex, int limit) {
    /* fastpath if the regex is a
    (1)one-char String and this character is not one of the
    RegEx's meta characters ".$|()[{^?*+\\", or
    (2)two-char String and the first char is the backslash and
    the second is not the ascii digit or ascii letter.
    */
    char ch = 0;
    if (((regex.value.length == 1 &&
    ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
    (regex.length() == 2 &&
    regex.charAt(0) == '\\' &&
    (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
    ((ch-'a')|('z'-ch)) < 0 &&
    ((ch-'A')|('Z'-ch)) < 0)) &&
    (ch < Character.MIN_HIGH_SURROGATE ||
    ch > Character.MAX_LOW_SURROGATE))
    {
    int off = 0;
    int next = 0;
    boolean limited = limit > 0;
    ArrayList<String> list = new ArrayList<>();
    while ((next = indexOf(ch, off)) != -1) {
    if (!limited || list.size() < limit - 1) {
    list.add(substring(off, next));
    off = next + 1;
    } else { // last one
    //assert (list.size() == limit - 1);
    list.add(substring(off, value.length));
    off = value.length;
    break;
    }
    }
    // If no match was found, return this
    if (off == 0)
    return new String[]{this};

    // Add remaining segment
    if (!limited || list.size() < limit)
    list.add(substring(off, value.length));

    // Construct result
    int resultSize = list.size();
    if (limit == 0) {
    while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
    resultSize--;
    }
    }
    String[] result = new String[resultSize];
    return list.subList(0, resultSize).toArray(result);
    }
    return Pattern.compile(regex).split(this, limit);
    }

    Stringsplit方法用于使用字符串的regex分割字符串,返回新的字符串数组。源码解析可以查看参考文献2。

参考文献

  1. Java serialVersionUID 有什么作用?
  2. Java String.split()源码分析
  3. Java8的String源码分析
  4. String源码浅谈
  5. Java String类源码分析

  String源码
You forgot to set the qrcode for Alipay. Please set it in _config.yml.
You forgot to set the qrcode for Wechat. Please set it in _config.yml.
You forgot to set the business and currency_code for Paypal. Please set it in _config.yml.
You forgot to set the url Patreon. Please set it in _config.yml.

评论