详解String类不可变的底层原理

发布于:2025-07-09 ⋅ 阅读:(13) ⋅ 点赞:(0)

String类

String的基本特性

  • 不可变性: String 对象一旦创建就不能被修改,所有看似修改的操作实际上都是创建新的 String 对象
  • final类: String 类被声明为 final,不能被继承
  • 基于字符数组: 内部使用final char value[]存储字符数据(Java9以后改为byte[] + 编码标记)

重要源码分析

关键字段

直接上源码:

/**
 * The value is used for character storage.
 *
 * @implNote This field is trusted by the VM, and is a subject to
 * constant folding if String instance is constant. Overwriting this
 * field after construction will cause problems.
 *
 * Additionally, it is marked with {@link Stable} to trust the contents
 * of the array. No other facility in JDK provides this functionality (yet).
 * {@link Stable} is safe here, because value is never null.
 */

/**
该字段用于字符存储。

实现说明:该字段被虚拟机(VM)信任,如果String实例是常量,它会成为常量折叠的优化对象。
在构造后覆盖此字段会导致问题。

此外,该字段标记了@Stable注解以信任数组内容。目前JDK中还没有其他设施提供此功能。
在此处使用@Stable是安全的,因为value永远不会为null。
*/

@Stable
private final byte[] value;

/**
 * The identifier of the encoding used to encode the bytes in
 * {@code value}. The supported values in this implementation are
 *
 * LATIN1
 * UTF16
 *
 * @implNote This field is trusted by the VM, and is a subject to
 * constant folding if String instance is constant. Overwriting this
 * field after construction will cause problems.
 */

/**
用于编码value字节的编码标识符。本实现中支持的值为:
LATIN1
UTF16

实现说明:该字段被虚拟机(VM)信任,如果String实例是常量,它会成为常量折叠的优化对象。
在构造后覆盖此字段会导致问题。
*/

private final byte coder;
  • 存储机制: 从Java9开始,String内部使用byte[]而不是char[]存储字符数据,这是为了支持紧凑型字符串

    • Java9之前使用char[](UTF-16编码,每个字符固定2字节)
    • 实际大部分业务字符串仅含Latin-1字符
    • byte[]可以根据内容动态选择编码
      • Latin-1:单字节存储ASCII字符(0~255)
      • UTF-16: 双字节存储扩展字符(如中文)
    • 性能对比:
    //Java 8 (char[])
    "Hello"  存储:10字节 (5 * 2字节)
    
    //Java 9+ (byte[] + Latin-1)
    "Hello"  存储:5字节 + 1字节(coder标记)
    //coder标记下文会解释
    
    • 性能权衡:
      • 访问字符时需要条件判断(检查coder值)
      • 内存节省的收益远大于条件判断的损耗
  • 关于value字段的注解:@Stable:

    • JDK内部注解
    • 表示字段引用及其内容在初始化后永远不变
    • 比常规final更强的不变性保证:
      • 普通fianl只保证引用不变
      • @Stable还保证数组元素不变
  • final修饰符解析

    • 修饰目标: 此处修饰的是byte[] value的引用(非数组内容)
    • 保证引用不可变(不可指向其他数组)
    • 举例:
    final byte[] value = new byte[10];
    value = new byte[20];	//编译错误(引用不可变)
    value[0] = 1;	//正确(数组内容可变性不由final决定)
    
    • 与String不可变性的关系
      • final是基础保障,但不是充分条件
      • 完整的不可变性需要:
        • 私有字段(private)
        • 不暴露内部数组(如toCharArray()返回副本)
        • 所有方法不修改数组内容

常用方法实现

equals,substring

equals()方法

源码解析:

/**
 * Compares this string to the specified object.  The result is {@code
 * true} if and only if the argument is not {@code null} and is a {@code
 * String} object that represents the same sequence of characters as this
 * object.
 *
 * <p>For finer-grained String comparison, refer to
 * {@link java.text.Collator}.
 *
 * @param  anObject
 *         The object to compare this {@code String} against
 *
 * @return  {@code true} if the given object represents a {@code String}
 *          equivalent to this string, {@code false} otherwise
 *
 * @see  #compareTo(String)
 * @see  #equalsIgnoreCase(String)
 */

/**
 * 将此字符串与指定对象进行比较。当且仅当参数不为 null 且是表示相同字符序列的 String 对象时,结果为 true。
 * 
 * 对于更精细的字符串比较,请参考 java.text.Collator。
 * 
 * @param  anObject 要与此字符串比较的对象
 * 
 * @return 如果给定对象表示与此字符串等效的 String,则返回 true,否则返回 false
 * 
 * @see  #compareTo(String)
 * @see  #equalsIgnoreCase(String)
 */

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    return (anObject instanceof String aString)
            && (!COMPACT_STRINGS || this.coder == aString.coder)
            && StringLatin1.equals(value, aString.value);
}
  1. 引用相等检查
public boolean equals(Object anObject) {
if (this == anObject) {
    return true;
}
  • 优化作用: 相同对象直接返回,避免后续计算
  1. 类型检查与模式匹配
return (anObject instanceof String aString)
  • Java16引入的模式匹配语法
    • 同时检查类型和转换类型
    • 将成功转换的对象赋值给新变量aString
  1. 紧凑型字符串兼容性检查
&& (!COMPACT_STRINGS || this.coder == aString.coder)
  • COMPACT_STRINGS: 静态final布尔值,表示是否启用紧凑字符串
    • 如果未启用紧凑字符串特性,则跳过编码检查
    • 如果启用,则要求两者的coder(编码器)相同
  1. 核心内容比较
&& StringLatin1.equals(value, aString.value);

实际的比较使用StringLatin1.equals(),先比较长度,再逐字节比较内容

substring()方法

源码解析:

/**
 * Returns a string that is a substring of this string. The
 * substring begins with the character at the specified index and
 * extends to the end of this string. <p>
 * Examples:
 * <blockquote><pre>
 * "unhappy".substring(2) returns "happy"
 * "Harbison".substring(3) returns "bison"
 * "emptiness".substring(9) returns "" (an empty string)
 * </pre></blockquote>
 *
 * @param      beginIndex   the beginning index, inclusive.
 * @return     the specified substring.
 * @throws     IndexOutOfBoundsException  if
 *             {@code beginIndex} is negative or larger than the
*             length of this {@code String} object.
 */

/**
 * 返回此字符串的子字符串。子字符串从指定索引处的字符开始,
 * 延伸到该字符串的末尾。
 * 
 * 示例:
 * <blockquote><pre>
 * "unhappy".substring(2) 返回 "happy"
 * "Harbison".substring(3) 返回 "bison"
 * "emptiness".substring(9) 返回 "" (空字符串)
 * </pre></blockquote>
 *
 * @param beginIndex 开始索引(包含该字符)
 * @return 指定的子字符串
 * @throws IndexOutOfBoundsException 如果 beginIndex 为负数或大于此字符串对象的长度
 */

public String substring(int beginIndex) {
  return substring(beginIndex, length());
}
  1. 方法重载
  • 这是一个便捷方法,委托给substring(int beginIndex, int endIndex)实现
  • 默认endIndex为字符串长度length
  1. 参数处理
  • beginIndex必须满足0 <= beginIndex <= length()

  • endIndex自动设置为字符串长度

  1. 边界情况
  • beginIndex == length()时,返回空字符串
  • beginIndex == 0时,返回原字符串的副本

接着来了解substring(int beginIndex, int endIndex)的实现原理

/**
 * Returns a string that is a substring of this string. The
 * substring begins at the specified {@code beginIndex} and
 * extends to the character at index {@code endIndex - 1}.
 * Thus the length of the substring is {@code endIndex-beginIndex}.
 * Examples:
 * <blockquote><pre>
 * "hamburger".substring(4, 8) returns "urge"
 * "smiles".substring(1, 5) returns "mile"
 * </pre></blockquote>
 *
 * @param      beginIndex   the beginning index, inclusive.
 * @param      endIndex     the ending index, exclusive.
 * @return     the specified substring.
 * @throws     IndexOutOfBoundsException  if the
 *             {@code beginIndex} is negative, or
 *             {@code endIndex} is larger than the length of
 *             this {@code String} object, or
 *             {@code beginIndex} is larger than
 *             {@code endIndex}.
 */

/**
 * 返回此字符串的子字符串。子字符串从指定的 beginIndex 开始,
 * 延伸到索引 endIndex - 1 处的字符。因此子字符串的长度为 endIndex - beginIndex。
 * 
 * 示例:
 * <blockquote><pre>
 * "hamburger".substring(4, 8) 返回 "urge"
 * "smiles".substring(1, 5) 返回 "mile"
 * </pre></blockquote>
 *
 * @param beginIndex 开始索引(包含该字符)
 * @param endIndex 结束索引(不包含该字符)
 * @return 指定的子字符串
 * @throws IndexOutOfBoundsException 如果 beginIndex 为负数,或
 *         endIndex 大于此字符串对象的长度,或
 *         beginIndex 大于 endIndex
 */

public String substring(int beginIndex, int endIndex) {
    int length = length();
    checkBoundsBeginEnd(beginIndex, endIndex, length);
    if (beginIndex == 0 && endIndex == length) {
        return this;
    }
    int subLen = endIndex - beginIndex;
    return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)
                      : StringUTF16.newString(value, beginIndex, subLen);
}
  1. 长度获取
int length = length();

先获取当前字符串长度,避免多次调用

  1. 边界检查
checkBoundsBeginEnd(beginIndex, endIndex, length);

通过checkBoundsBeginEnd()方法验证:

  • beginIndex >= 0
  • endIndex <= length
  • beginIndex <= endIndex

如果不满足则抛出IndexOutOfBoundsException

  1. 完整字符串优化
if (beginIndex == 0 && endIndex == length) {
    return this;
}

请求整个字符串时直接返回原对象

  1. 字串长度计算
int subLen = endIndex - beginIndex;
  1. 判断不同编码创建子串
return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)	:	StringUTF16.newString(value, beginIndex, subLen);

利用了Java9的紧凑型字符串特性,根据不同编码类型选择不同的创建方式

String不可变性的原理

一、不可变性的本质定义
String 对象一旦创建,其内容永远不可更改,所有看似修改的操作都返回新对象。

二、底层实现机制

  1. 存储结构锁定
1. // Java 8及以前
 private final char[] value; // final保证引用不变

// Java 9+优化
@Stable 
private final byte[] value; // 字节存储+稳定性注解
private final byte coder; // 编码标记  
  1. 无修改接口
    所有修改操作都创建新对象:
public String concat(String str) {
    return new String(...);
}
//不提供任何修改内部数组的方法

三、设计保障措施

  1. 构造防护
public String(char[] value) {
this.value = Arrays.copyOf(value, value.length); // 防御性拷贝
}
  1. 访问控制
public char[] toCharArray() {
return Arrays.copyOf(value, length()); // 返回副本而非原数组
}  
  1. 反射防护
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
field.set(str, newValue); // 抛出IllegalAccessException  

四、关键技术支撑

  1. 哈希缓存优化
private int hash; // 首次调用hashCode()时计算并缓存

public int hashCode() {
  int h = hash;
  if (h == 0 && !hashIsZero) {
      h = calculateHash();
      hash = h;
  }
  return h;
}
  1. 字符串常量池
String s2 = new String("abc"); // 堆中新对象
s2.intern(); // 返回常量池引用
String s1 = "abc"; // 常量池对象
  1. 线程安全保证
    • 天然线程安全,无需同步
    • 可安全发布(Safe Publication)

五、不可变性的核心价值

优势领域 具体表现
安全性 防止敏感数据被篡改
性能 哈希缓存、常量池复用
线程安全 无需同步自由共享
设计简单性 消除状态变化复杂性

六、典型应用场景

  1. 作为HashMap的Key
config.put("timeout", 30); // 依赖哈希缓存
Map<String, Object> config = new HashMap<>();
  1. 类加载机制
Class.forName("com.example.Test"); // 类名字符串不可变
  1. 网络通信
URL url = new URL("https://example.com"); // 基础地址安全

网站公告

今日签到

点亮在社区的每一天
去签到