当前位置: 首页 > news >正文

Java String

JDK 9 String底层从char数组改为byte数组的原因

JDK 9之所以要将String底层的char数组改为byte数组,核心目的就是为了节省内存,避免不必要的资源浪费。

在JDK 9之前,String的底层存储依赖于char数组,并且采用UTF-16编码格式,这就意味着不管字符本身是否复杂,每个字符都固定占用2个字节。但在实际的开发场景中,我们用到的大量字符串其实都是纯英文、数字这类简单字符,比如日常接触的URL、JSON的键名、系统日志内容等,这些字符根本不需要2个字节来存储,用1个字节就足够表示,这种固定的双字节存储方式,无疑造成了大量的内存浪费。

为了解决这个问题,JDK 9引入了“紧凑字符串”(Compact Strings)的优化机制,将String底层的存储结构从char数组改成了byte数组,同时新增了一个coder字段,用来标记当前字符串的编码方式。这样一来,就能实现“按需编码”:如果字符串中全是Latin-1类型的字符(也就是纯英文、数字等简单字符),就采用单字节编码存储,最大程度节省内存;如果字符串中包含中文、特殊符号等非Latin-1字符,就自动切换到UTF-16双字节编码,保证字符能正常存储和展示。

我们可以举一个简单的例子来直观感受这种优化的效果:比如存储“Hello”这5个英文字符,在JDK 8中,用char数组存储时,每个字符占用2个字节,5个字符总共需要10个字节;而在JDK 9及以后,用byte数组配合Latin-1编码存储,每个字符只占用1个字节,5个字符总共只需要5个字节,内存占用直接减少了一半,优化效果非常明显。


String、StringBuffer、StringBuilder 的区别

1、可变性(最核心区别)

String 是不可变的,简单说就是一旦创建了 String 对象,它的内容就再也不能修改了。比如我们用“+”拼接字符串时,看似是修改了原字符串,实际上是新建了一个全新的 String 对象,原对象并没有发生变化。

StringBuilder 和 StringBuffer 都是可变的,核心原因在于它们继承自 AbstractStringBuilder 类,底层用于存储字符串的字符数组没有被 final 修饰,也没有像 String 那样的访问权限限制,支持直接修改数组内容。比如调用 append 方法时,是直接在原有字符数组上添加内容,不需要新建对象,这也是它们与 String 最关键的区别。

2、线程安全性

String 因为是不可变的,相当于常量,所以String 的线程是安全的,多个线程同时访问它,也不会出现内容被篡改的情况。

AbstractStringBuilder 是 StringBuilder 与 StringBuffer`的公共父类,定义了 append、insert 等基础操作方法,但本身没有做线程安全的处理。其中StringBuffer 为了保证线程安全,给这些方法加了同步锁,所以多线程环境下使用它,不会出现数据混乱的问题;而StringBuilder 没有加任何同步锁,效率更高,但在多线程环境下使用,可能会出现数据安全问题,属于非线程安全。

3、性能对比

性能排序:StringBuilder > StringBuffer > String

String 的性能最差,因为每次修改都会新建对象,频繁操作会产生大量垃圾对象,拖慢程序速度;StringBuffer 因为加了同步锁,每次操作都要获取和释放锁,有一定的性能开销,所以速度中等;StringBuilder 去掉了同步锁,不用额外处理线程安全,所以在相同操作下,它的速度最快,比 StringBuffer 大概能提升 10%~15% 的性能,但前提是单线程环境。

4、使用场景总结

如果只是操作少量字符串,比如定义常量、简单赋值,直接用 String 就好,简洁又方便。

如果是单线程环境下,需要大量拼接、修改字符串(比如循环拼接、拼接复杂文本),优先用 StringBuilder,兼顾效率和便捷性。

如果是多线程环境下,需要操作大量字符串,必须用 StringBuffer,保证线程安全,避免数据错乱。


字符串拼接用“+”还是StringBuilder?

我们平时用“+”拼接字符串时,其实底层并不是直接拼接,而是先创建一个StringBuilder对象,通过它的append()方法把要拼接的内容一个个加进去,等拼接完成后,再调用toString()方法,最终得到一个String类型的对象。

但要注意,要是在循环里用“+”拼接字符串,就会有明显的问题:编译器不会重复利用同一个StringBuilder对象,而是每次循环都会新建一个StringBuilder,这样一来,循环次数多了,就会创建大量多余的StringBuilder对象,不仅浪费内存,还会拖慢程序运行速度。

如果我们直接创建一个StringBuilder对象,然后用它的append()方法进行拼接,就不会出现这种问题——全程只用到一个StringBuilder对象,既节省资源,也能提高拼接效率。


Java String 类核心方法总结表

分类方法签名方法作用核心说明 / 使用场景
获取基本信息int length()获取字符串长度返回字符个数(UTF-16 码元数)
boolean isEmpty()判断是否为空串等价于length() == 0
boolean isBlank()判断是否为空白串JDK11+,空 / 全空格 / 制表符都返回 true
字符操作char charAt(int index)获取指定索引字符索引从 0 开始
char[] toCharArray()转换为字符数组复制字符串所有字符到新数组
void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)复制字符到目标数组批量拷贝字符,效率更高
比较判断boolean equals(Object obj)内容相等判断区分大小写,空值安全需先判空
boolean equalsIgnoreCase(String s)忽略大小写相等判断不区分大小写比较内容
int compareTo(String s)字典序比较返回负数 / 0 / 正数,实现 Comparable
int compareToIgnoreCase(String s)忽略大小写字典序比较不区分大小写排序
boolean contentEquals(CharSequence cs)与字符序列比较支持和 StringBuffer/StringBuilder 比较
包含 / 前缀 / 后缀boolean contains(CharSequence s)判断是否包含子串JDK1.5+,底层调用 indexOf
boolean startsWith(String prefix)判断是否以指定前缀开头常用于路径、文件名校验
boolean endsWith(String suffix)判断是否以指定后缀结尾常用于文件格式校验
查找索引int indexOf(int ch/String str)查找字符 / 子串首次出现位置未找到返回 -1
int indexOf(..., int fromIndex)从指定索引开始查找支持自定义起始位置
int lastIndexOf(...)查找字符 / 子串最后出现位置反向查找
截取子串String substring(int beginIndex)从指定索引截取到末尾左闭右开,返回新字符串
String substring(int begin, int end)截取指定范围子串包含 begin,不包含 end
CharSequence subSequence(int begin, int end)截取字符序列实现 CharSequence 接口的方法
拼接替换String concat(String str)拼接字符串等价于+,返回新串
String replace(char old, char new)替换所有指定字符单字符全局替换
String replace(CharSequence target, CharSequence replacement)替换子串字面量替换,非正则
String replaceAll(String regex, String repl)正则全局替换支持正则表达式匹配替换
String replaceFirst(String regex, String repl)正则替换第一个匹配项只替换首次匹配的内容
分割 / 拼接String[] split(String regex)正则分割字符串常用分隔符:,、、-
String[] split(String regex, int limit)限制分割次数limit 控制结果数组长度
static String join(CharSequence delimiter, ...)用分隔符拼接多个字符串JDK1.8+,批量拼接更简洁
大小写转换String toLowerCase()转小写使用默认区域设置
String toLowerCase(Locale locale)按指定区域转小写适配多语言场景
String toUpperCase()转大写同上
去空操作String trim()去除首尾空白(≤U+0020)传统去空格,不包含全角空格
String strip()去除首尾空白(Unicode 标准)JDK11+,支持所有空白字符
String stripLeading()去除首部空白JDK11+
String stripTrailing()去除尾部空白JDK11+
编码 / 字节转换byte[] getBytes()按默认编码转字节数组跨平台可能乱码,推荐指定编码
byte[] getBytes(String charsetName)按指定编码转字节数组UTF-8GBK
byte[] getBytes(Charset charset)按字符集对象转字节数组JDK1.6+,推荐使用
重复 / 格式化String repeat(int count)重复字符串指定次数JDK11+,快速生成重复字符串
static String format(String format, Object... args)格式化字符串类似 C 语言 printf,拼接更优雅
高级功能Stream<String> lines()按行分割为流JDK11+,处理多行文本
boolean matches(String regex)正则匹配判断校验手机号、邮箱等格式
String intern()字符串入常量池本地方法,优化内存、提升比较效率
静态转换static String valueOf(任意类型)基本类型 / 对象转字符串安全转换,null 转"null"
http://www.jsqmd.com/news/558852/

相关文章:

  • 2026年靠谱的交流低压配电柜/河南交流低压配电柜/河南高低压配电柜/配电柜配电箱精选厂家 - 行业平台推荐
  • 2026 HENF级板材品牌如何选择?环保与性能双优指南 - 品牌排行榜
  • 告别原生组件坑!微信小程序里让Canvas乖乖跟着ScrollView滚动的3种实战方案
  • 工业质检新视野:通义千问3-VL-Reranker-8B在缺陷检测中的应用
  • 2026年比较好的广州石锅商用烤箱/面包商用烤箱/石锅商用烤箱/食品商用烤箱制造厂家 - 行业平台推荐
  • NeRF训练太慢?从Blender数据到位置编码,这5个关键细节决定了你的GPU燃烧效率
  • 2026年质量好的ALD技术/ALD设备/光伏ALD/ALD工艺开发供应商怎么选 - 行业平台推荐
  • 视频字幕提取效率提升10倍:本地AI驱动的硬字幕解决方案全指南
  • StructBERT零样本分类-中文-base高性能:ONNX Runtime加速推理延迟降低65%
  • python高校大学生家教平台的设计与开发
  • 前端开发者必看:5个提升AI提示词效果的实战技巧(附代码示例)
  • Fish Speech-1.5语音合成企业标准:WAV采样率/比特率/声道数配置指南
  • 无序关联容器:unordered map和unordered multimap 详解
  • LeagueAkari:终极英雄联盟游戏助手完全指南
  • 春节不用愁对联:春联生成模型实战,3步生成专属春联
  • SerialMP3库:GD3300D/TD5580A串口MP3模块驱动详解
  • 【深度解析】CODrone:如何用高分辨率多视角数据重塑无人机旋转目标检测基准
  • 比迪丽LoRA模型动态光影效果集:展现复杂光线下的角色魅力
  • 各版本易筋经意识层操作的系统动力学分析
  • Kubernetes 存储管理最佳实践
  • SiameseUIE效果展示:终南山隐居王维等文化地理关系还原
  • 英雄联盟段位修改完整解决方案:LeaguePrank免费工具终极指南
  • ROS2 Humble + Gazebo 保姆级安装与模型导入教程(含国内镜像加速)
  • DeEAR镜像免配置实战:无需修改config.py,直接运行app.py启用全部功能模块
  • 解析RK3566平台双摄(OV5648+GC2145)的Split Mode配置实战
  • Qwen3-ASR-1.7B多说话人分离展示:会议录音自动分角色
  • OpenClaw 的模型架构中,层归一化采用的是 Pre-LN 还是 Post-LN?
  • Guohua Diffusion 快速入门:三步完成星图GPU平台一键部署
  • RWKV7-1.5B-G1A集成Python爬虫实战:智能数据采集与清洗方案
  • Qwen3-Reranker-0.6B快速体验:搭建个人语义排序服务的简单方法