Java 并没有定义 String 类型,仅在标准库中预先定义了一个 String 类,每个字符串都是该类的一个实例。

String 的内部结构

string.svg

在 JDK 6 及之前版本中,String 对象通过 offset 和 count 两个属性来定位 char[ ] 数组,获取字符串。这么做可以高效、快速地共享数组对象,同时节省内存空间,但这种方式很有可能会导致内存泄漏。

从 JDK 7 开始,String 类中不再有 offset 和 count 两个变量。这样的好处是解决了 substring() 方法因共享 char[ ] 数组对象而可能导致的内存泄漏问题。

从 JDK 9 开始,String 类将数组元素类型从占用 2 个字节的 char 类型变为了占用 1 个字节的 byte 类型,这个改变基于 JEP 254

  1. private final char value[]; // before JDK 9
  2. private final byte[] value; // since JDK 9

如果一个字符串仅包含 Latin-1 字符,那么每个字符仅占用 1 个字节。否则字符串的每个字符采用 UTF-16 编码,即每个字符占用 2 个字节。

因此 String 类新增了一个 byte 类型的属性 coder,用于标识每个字符占用多少个字节,coder 属性有两个值可选 —— Latin-1 和 UTF-16。

  1. static final byte LATIN1 = 0;
  2. static final byte UTF16 = 1;

所以 String 类中的大部分方法,在执行前都要先判断字符串字符的编码格式。下面以 indexOf() 方法为例,展示了内部的判断过程。

  1. public int indexOf(int ch, int fromIndex) {
  2. return isLatin1() ? StringLatin1.indexOf(value, ch, fromIndex)
  3. : StringUTF16.indexOf(value, ch, fromIndex);
  4. }
  5. private boolean isLatin1() {
  6. return COMPACT_STRINGS && coder == LATIN1;
  7. }

关于 JDK 9 的改变,可以参考 Evolution of Strings in Java to Compact Strings

String 的不可变性

String 的不可变型是字符串最重要的特征,即 String 类没有提供可以更改已有字符串字符的方法。

那为什么 Java 中的 String 要设计成不可变呢,主要是有以下三个方面的考虑:

  1. 保证 String 对象的安全性。假设 String 对象是可变的,那么 String 对象将可能被恶意修改。
  2. 保证 hash 属性值不会频繁变更,确保了唯一性,使得类似 HashMap 容器才能实现相应的 key-value 缓存功能。
  3. 可以实现字符串常量池。