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

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"); // 基础地址安全

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/87809.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/87809.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GIT: 一个用于视觉与语言的生成式图像到文本转换 Transformer

摘要 在本文中&#xff0c;我们设计并训练了一个生成式图像到文本转换 Transformer——GIT&#xff0c;以统一视觉-语言任务&#xff0c;如图像/视频字幕生成和问答。虽然生成式模型在预训练和微调之间提供了一致的网络架构&#xff0c;但现有工作通常包含复杂的结构&#xff…

20250706-9-Docker快速入门(下)-Docker在线答疑_笔记

一、Kubernetes核心概念与集群搭建 1. 在线答疑 &#xfeff; 1&#xff09;答疑Docker需要掌握到什么程度 学习目标&#xff1a;达到入门水平即可&#xff0c;重点掌握第一章Docker入门视频内容学习建议&#xff1a;预习时间约3-4小时&#xff0c;建议吸收视频内容的80%学…

Node.js-http模块

HTTP 协议 概念 HTTP&#xff08;hypertext transport protocol&#xff09;协议&#xff1b;中文叫超文本传输协议,是一种基于TCP/IP的应用层通信协议这个协议详细规定了 浏览器 和万维网 服务器 之间互相通信的规则。协议中主要规定了两个方面的内容 客户端&#xff1a;用来…

Java JDBC的初步了解

文章目录 基本流程注册驱动的两种方法DriverManagerDriverManager 的核心作用核心原理自动注册驱动的机制关键方法 示例代码: 连接Mysql数据库StatementPreparedStatement JDBC全称Java DataBase Connectivity。 定义: JDBC 是 Java 语言中用于连接和执行 SQL 操作的标准接口。…

[netty5: ChunkedInput ChunkedWriteHandler]-源码分析

ChunkedInput ChunkedInput<B> 是 Netty 中用于按块读取不定长数据流的接口&#xff0c;常配合 ChunkedWriteHandler 实现流式写入&#xff0c;支持如文件、流、HTTP 和 WebSocket 等多种数据源。 实现类简要说明ChunkedFile用于将常规文件按块传输&#xff08;使用传统…

QT 第十二讲 --- 控件篇 LineEdit,TextEdit与ComboBox

前言&#xff1a;欢迎进入 QT 控件世界的第十二讲&#xff01;在上一讲《QT 第十一讲 --- 控件篇 LCDnumber&#xff0c;ProgressBar与CalenderWidget》中&#xff0c;我们探索了用于信息展示和状态反馈的控件&#xff1a;精准的数字显示器 LCD Number、直观的进度指示器 Progr…

VSCode遇到的一些小毛病(自动保存、运行后光标不再处于编辑区)

1. 右键点击Run Code没有触发自动保存 1. 打开 VS Code 设置&#xff08;Ctrl ,&#xff09; 2. 搜索&#xff1a;code runner save 3. 勾选你需要的 2. 运行后光标仍然处于编辑区&#xff08;容易误输入&#xff09; 1. 打开 VS Code 设置&#xff08;Ctrl ,&#xff09; 2.…

Maixcam的使用2

1.单文件和项目&#xff08;多个 py 文件项目/模块化&#xff09;# 在编写代码时&#xff0c;一般两种模式&#xff0c;执行单个文件&#xff0c;或者执行一个完成项目&#xff08;包含多个 py 文件或者其它资源文件&#xff09;。 单文件模式&#xff1a;MaixVision 创建或者…

征信系统架构思想:打造商业信任基石_东方仙盟—仙盟创梦IDE

一、建设必要性在复杂的商业环境中&#xff0c;企业面临多元交易对象与业务场景&#xff0c;准确评估合作方信用状况及潜在价值的难度显著增加。传统经验判断和简单背景调查存在局限性&#xff0c;难以满足现代商业决策需求&#xff0c;因此构建科学的征信体系具有现实必要性。…

网安-XSS-pikachu

介绍 XSS&#xff0c;即跨站脚本攻击&#xff0c;是指攻击者利用Web服务器中的代码漏洞&#xff0c;在页面中嵌入客户端脚本&#xff08;通常是一段由JavaScript编写的恶意代码&#xff09;&#xff0c;当信任此Web服务器的用户访问 Web站点中含有恶意脚本代码的页面&#xff…

算法入门——字典树(C++实现详解)

字典树&#xff08;Trie&#xff09;是处理字符串匹配的高效数据结构&#xff0c;广泛应用于搜索提示、拼写检查等场景。本文将带你从零掌握字典树的原理与实现&#xff01; 一、什么是字典树&#xff1f; 字典树&#xff08;Trie&#xff09;是一种树形数据结构&#xff0c;…

SpringBoot整合SpringCache缓存

SpringBoot整合SpringCache使用缓存 文章目录SpringBoot整合SpringCache使用缓存1.介绍2.SpringBoot整合1.导入xml依赖2.配置yml3.使用EnableCaching启用SpringCache4.Cacheable5.CachePut6.CacheEvict7. Caching8.CacheConfig3.其他属性配置1.keyGenerator 属性2. cacheManage…

WPF学习笔记(20)Button与控件模板

Button与控件模板一、 Button默认控件模板详解二、自定义按钮模板一、 Button默认控件模板详解 WPF 中的大多数控件都有默认的控件模板。 这些模板定义了控件的默认外观和行为&#xff0c;包括控件的布局、背景、前景、边框、内容等。 官方文档&#xff1a;https://learn.mic…

蓝天居士自传(1)

蓝天居士何许人&#xff1f; 蓝天居士是我的笔名&#xff0c;也可以说是号。就好像李白号青莲居士、欧阳修号六一居士一样。笔者本名彭昊 —— 一个有不少重名重姓者的名字。 笔者小的时候上语文课&#xff0c;无论是小学、初中抑或是高中&#xff0c;都会有鲁迅&#xff08;…

短剧系统开发定制全流程解析:从需求分析到上线的专业指南

一、短剧行业数字化趋势与系统开发必要性在短视频内容爆发式增长的时代背景下&#xff0c;短剧作为一种新兴的内容形式正在迅速崛起。数据显示&#xff0c;2023年中国短剧市场规模已突破300亿元&#xff0c;用户规模达到4.5亿&#xff0c;年增长率超过200%。这一迅猛发展的市场…

getBoundingClientRect() 详解:精准获取元素位置和尺寸

getBoundingClientRect() 是 JavaScript 中一个强大的 DOM API&#xff0c;用于获取元素在视口中的精确位置和尺寸信息。它返回一个 DOMRect 对象&#xff0c;包含元素的坐标、宽度和高度等关键几何信息。 基本用法 const element document.getElementById(myElement); cons…

EXCEL 基础技巧

来源&#xff1a;WPS 官网 初步了解WPS表格-WPS学堂https://www.wps.cn/learning/course/detail/id/635.html 1、格式刷 1.1使用格式刷隔行填充颜色。 首先设置部分表格颜色&#xff0c;选中此区域&#xff0c;双击点击格式刷&#xff0c;然后选中其他表格区域。 这样就可以…

【RK3568 编译rtl8723DU驱动】

RK3568 编译rtl8723DU驱动 编译源码1.解压rtl8723du2.修改Makefile 验证1.加载模块2.开启wifi 在驱动开发中&#xff0c;驱动的编译与集成是实现设备功能的关键环节。本文聚焦于基于 RK3568 处理器平台编译 RTL8723DU WiFi/BT 二合一模块驱动的完整流程&#xff0c;涵盖源码编译…

基于Simulink的二关节机器人独立PD控制仿真

文章目录 理论模型仿真窗口控制函数目标函数仿真 本文是刘金琨. 机器人控制系统的设计与MATLAB仿真的学习笔记。 理论模型 对于二关节机器人系统&#xff0c;其动力学模型为 D ( q ) q C ( q , q ˙ ) q ˙ r D(q)\ddot qC(q,\dot q)\dot q r D(q)q​C(q,q˙​)q˙​r 式…

【技术架构解析】国产化双复旦微FPGA+飞腾D2000核心板架构

本文就一款基于飞腾D2000核心板与两片高性能FPGA的国产化开发主板进行技术解析&#xff0c;包括系统架构、主要硬件模块、关键接口及软件环境&#xff0c;重点阐述各子系统间的数据路径与协同工作方式&#xff0c;旨在为行业内同类产品设计与应用提供参考。 随着国产化要求的加…