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

【从零开始java学习|第十三篇】字符串究极知识总结

目录

一、什么是字符串

二、String 概述:不可变的文本载体

2.1 本质与核心特性

2.2 不可变性的原因

2.3 不可变性的好处

2.4 JDK 版本差异:底层存储优化(JDK 8 vs JDK 9+)

三、String 的构造方法与内存分析

3.1 常用构造方法

(1)无参构造:new String()

(2)字面量构造:new String(String original)

​编辑

(3)字符数组构造:new String(char[] value)

(4)字节数组构造:new String(byte[] bytes)

(5)StringBuilder 构造:new String(StringBuilder sb)

3.2 两种创建方式的内存对比(关键考点)

四、字符串的比较:地址 vs 内容

4.1  == 运算符:比较对象地址

4.2 equals()方法:比较字符串内容

4.3 intern()方法:获取常量池引用

4.4 常见比较场景与易错点

(1)字符串拼接的地址比较

(2)空字符串与null的比较

五、StringBuilder:可变字符串的解决方案

5.1 核心特性

5.2 基本操作(代码示例)

(1)构造方法

(2)常用方法

5.3 底层原理:扩容机制

5.4 效率对比:String vs StringBuilder

六、StringJoiner:简化分隔符拼接

6.1 核心特性

6.2 基本操作(代码示例)

(1)构造方法

(2)常用方法

6.3 底层实现

七、字符串相关类的底层原理深度解析

7.1 String 底层:不可变的本质

7.2 StringBuilder 底层:可变的核心

7.3 StringJoiner 底层:封装的便捷性

八、总结与实践建议

8.1 三类字符串类的适用场景

8.2 性能优化建议


一、什么是字符串

字符串是 Java 开发中最常用的数据类型之一,用于表示文本信息。Java 提供了三类核心字符串处理类:String(不可变字符串)、StringBuilder(可变字符串,单线程)、StringJoiner(简化分隔符拼接,Java 8+)。本文将从String概述出发,逐步深入构造方法、比较逻辑、可变字符串操作,最终解析底层原理,帮助开发者全面掌握字符串知识。

二、String 概述:不可变的文本载体

2.1 本质与核心特性

String引用数据类型,但不属于 Java 基本类型,其核心特性是不可变性—— 字符串对象创建后,其内容(字符序列)无法被修改。

String s = "abc";
s += "d"; // 看似修改,实际是创建新对象"abcd",原"abc"仍存在
System.out.println(s); // 输出"abcd",但原"abc"未被修改

2.2 不可变性的原因

String的不可变性由底层存储和关键字共同保证(以 JDK 8 为例):

public final class String {// 存储字符的数组,private + final:数组引用不可变,外部无法直接修改数组private final char[] value; // 哈希值缓存(不可变故只需计算一次)private int hash; // 无其他修改value数组的方法(如setCharAt())
}

  • final classString不能被继承,避免子类破坏不可变性;
  • private final char[] valuevalue数组引用不可变(不能指向新数组),且private修饰让外部无法直接操作数组;
  • 无修改方法:String未提供修改数组元素的方法(如value[i] = 'x'),所有 “修改” 操作(如substringconcat)都会返回新String对象。

2.3 不可变性的好处

  1. 线程安全:不可变对象天生线程安全,无需同步;
  2. 常量池复用:相同字面量的String可复用常量池中的对象,节省内存;
  3. 哈希值缓存hash值仅计算一次,提升HashMap等集合的效率。

2.4 JDK 版本差异:底层存储优化(JDK 8 vs JDK 9+)

  • JDK 8 及之前:用char[] value存储字符(每个char占 2 字节,UTF-16 编码);
  • JDK 9 及之后:用byte[] value + byte coder存储(coder标识编码:0=ISO-8859-1,1=UTF-16)。
    优化原因:大部分字符串是拉丁字符(如英文字母),用byte[](1 字节 / 字符)可节省 50% 内存。

三、String 的构造方法与内存分析

String提供多种构造方法,不同方法的内存分配逻辑不同,直接影响对象复用和内存占用。

3.1 常用构造方法

(1)无参构造:new String()

创建空字符串对象,底层复用常量池中的空byte/char数组。

String s1 = new String(); 
// 内存逻辑:
// 1. 常量池检查是否有"空字符串"(长度为0的数组),无则创建;
// 2. 堆中创建String对象,s1的value指向常量池的空数组;
// 3. s1 != ""(堆对象vs常量池引用),但s1.equals("")为true。
(2)字面量构造:new String(String original)

根据已有字符串创建新对象,优先复用常量池中的字面量。

String s2 = new String("abc"); 
// 内存逻辑(JDK 8):
// 1. 常量池检查是否有"abc"的char数组,无则创建;
// 2. 堆中创建新String对象,s2的value指向常量池的"abc"数组;
// 3. 此时:"abc"(常量池引用)和s2(堆对象)地址不同,但内容相同。
(3)字符数组构造:new String(char[] value)

将字符数组转为String复制数组而非直接引用,保证不可变性。

char[] arr = {'a', 'b', 'c'};
String s3 = new String(arr); 
arr[0] = 'd'; // 修改原数组
System.out.println(s3); // 输出"abc"(s3的value是复制后的数组,不受原数组影响)
// 内存逻辑:
// 1. 堆中创建新char数组(复制arr的内容);
// 2. 堆中创建String对象,s3的value指向新数组;
// 3. 原arr修改不影响s3,体现不可变性。
(4)字节数组构造:new String(byte[] bytes)

将字节数组按默认编码(如 UTF-8)转为String,同样复制字节数组。

byte[] bytes = "abc".getBytes(); // 按UTF-8编码转为字节数组
String s4 = new String(bytes); 
System.out.println(s4); // 输出"abc"
(5)StringBuilder 构造:new String(StringBuilder sb)

StringBuilder的内容转为String,底层复制StringBuilder的字符数组。

StringBuilder sb = new StringBuilder("abc");
String s5 = new String(sb); 
// 内存逻辑:复制sb的char[] value,s5的value指向新数组,sb后续修改不影响s5。

3.2 两种创建方式的内存对比(关键考点)

创建方式内存位置是否复用常量池地址比较结果(示例)
String s = "abc"常量池(引用指向常量池)是(优先复用)s == "abc" → true
String s = new String("abc")堆(对象在堆,value 指向常量池)是(常量池复用)s == "abc" → false(堆 vs 常量池)

示例验证

String s6 = "abc";          // 常量池引用
String s7 = new String("abc"); // 堆对象
System.out.println(s6 == s7); // false(地址不同)
System.out.println(s6.equals(s7)); // true(内容相同)

四、字符串的比较:地址 vs 内容

字符串比较是高频考点,需区分==(地址比较)和equals()(内容比较),以及intern()方法的作用。

4.1  == 运算符:比较对象地址

  • 若比较的是String引用,判断两者是否指向同一对象
  • 若比较的是String与基本类型(如"123" == 123),会触发类型转换,通常返回 false。

示例

String s8 = "abc";
String s9 = "abc"; // 复用常量池,与s8指向同一对象
String s10 = new String("abc"); // 堆对象,地址不同System.out.println(s8 == s9); // true(同一常量池对象)
System.out.println(s8 == s10); // false(堆vs常量池)

4.2 equals()方法:比较字符串内容

String重写了Objectequals()方法,核心逻辑是逐字符比较内容,步骤如下:

// String的equals()源码(JDK 8)
public boolean equals(Object anObject) {1. 先判断地址是否相同,相同则直接返回true;if (this == anObject) {return true;}2. 判断参数是否为String类型,不是则返回false;if (anObject instanceof String) {String anotherString = (String)anObject;3. 比较字符数组长度,不同则返回false;int n = value.length;if (n == anotherString.value.length) {4. 逐字符比较,有不同则返回false;char v1[] = value;char v2[] = anotherString.value;int i = 0;while (n-- != 0) {if (v1[i] != v2[i])return false;i++;}5. 所有字符相同,返回true;return true;}}return false;
}

示例

String s11 = new String("abc");
String s12 = new String("abc");
System.out.println(s11 == s12); // false(堆中两个不同对象)
System.out.println(s11.equals(s12)); // true(内容相同)

4.3 intern()方法:获取常量池引用

intern()String的 native 方法,作用是:若常量池中存在当前字符串的字面量,则返回常量池引用;若不存在,则将当前字符串加入常量池并返回引用。

示例

String s13 = new String("abc");
String s14 = s13.intern(); // 返回常量池"abc"的引用
System.out.println(s14 == "abc"); // true(s14指向常量池)
System.out.println(s13 == s14); // false(s13在堆,s14在常量池)

4.4 常见比较场景与易错点

(1)字符串拼接的地址比较
  • 编译期优化:字面量拼接(如"a"+"b")会被编译器优化为"ab",复用常量池;
  • 运行期拼接:变量拼接(如a+"b")会创建StringBuilder,最终返回堆对象,不复用常量池。

示例

String s15 = "ab";
String s16 = "a" + "b"; // 编译期优化为"ab",指向常量池
String a = "a";
String s17 = a + "b"; // 运行期拼接,返回堆对象System.out.println(s15 == s16); // true(编译期优化)
System.out.println(s15 == s17); // false(堆vs常量池)
(2)空字符串与null的比较
  • "":空字符串对象(有地址,内容为空);
  • null:无对象(无地址,不指向任何内存)。

示例

String s18 = "";
String s19 = null;
System.out.println(s18.equals(s19)); // false(内容比较,null不触发NPE)
System.out.println(s19.equals(s18)); // 抛出NullPointerException(null调用方法)

五、StringBuilder:可变字符串的解决方案

String的不可变性导致频繁拼接时创建大量临时对象,效率极低。StringBuilder通过可变字符数组解决此问题,适用于单线程下的字符串修改场景。

5.1 核心特性

  • 可变:底层用非finalchar[] value存储字符,支持直接修改数组;
  • 单线程安全:无同步锁(多线程需用StringBuffer,但效率低);
  • 高效:修改时无需创建新对象,仅在数组容量不足时扩容。

5.2 基本操作(代码示例)

(1)构造方法
// 1. 默认构造:初始容量16
StringBuilder sb1 = new StringBuilder();
// 2. 指定初始容量:避免频繁扩容
StringBuilder sb2 = new StringBuilder(32);
// 3. 用字符串初始化:容量=字符串长度+16
StringBuilder sb3 = new StringBuilder("abc");
(2)常用方法
方法功能示例结果
append(任意类型)追加内容到末尾sb3.append("d").append(123)"abcd123"
insert(int idx, 内容)在指定索引插入内容sb3.insert(2, "xy")"abxycd123"
delete(int start, int end)删除 [start, end) 的字符sb3.delete(2,4)"abcd123"
reverse()反转字符串sb3.reverse()"321dcba"
toString()转为String(复制数组)sb3.toString()"321dcba"

示例

StringBuilder sb = new StringBuilder("Java");
sb.append(" ").append("is").append(" ").append("easy"); // 追加
sb.insert(4, "SE"); // 插入
System.out.println(sb); // 输出"JavaSE is easy"
sb.reverse();
System.out.println(sb); // 输出"ysae si ESavaJ"

5.3 底层原理:扩容机制

StringBuilder继承自AbstractStringBuilder,底层逻辑由父类实现:

  1. 初始容量:默认 16,或字符串长度+16(初始化时);
  2. 扩容触发:当追加内容后总长度超过当前容量时;
  3. 扩容规则
    • 计算新容量:newCapacity = 原容量 * 2 + 2
    • 若新容量仍不足,则用 “所需最小容量” 作为新容量;
    • Arrays.copyOf()复制原数组到新数组。

示例:初始容量 16 的StringBuilder,追加 20 个字符时:

  • 原容量 16 < 20 → 扩容为16*2+2=34 → 复制数组到新容量 34 的数组。

5.4 效率对比:String vs StringBuilder

// 1. String拼接(效率低)
long start1 = System.currentTimeMillis();
String s = "";
for (int i = 0; i < 10000; i++) {s += i; // 每次创建新对象
}
long end1 = System.currentTimeMillis();
System.out.println("String耗时:" + (end1 - start1) + "ms"); // 约1000ms(视环境)// 2. StringBuilder拼接(效率高)
long start2 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {sb.append(i); // 仅修改数组
}
String result = sb.toString();
long end2 = System.currentTimeMillis();
System.out.println("StringBuilder耗时:" + (end2 - start2) + "ms"); // 约1ms

六、StringJoiner:简化分隔符拼接

Java 8 新增StringJoiner,专门解决 “多元素拼接 + 分隔符 + 前后缀” 场景(如拼接列表为[a, b, c]),避免手动处理最后一个分隔符的问题。

6.1 核心特性

  • 简化分隔符:无需判断 “是否为最后一个元素”;
  • 支持前后缀:统一添加前缀(如[)和后缀(如]);
  • 底层依赖 StringBuilder:效率与StringBuilder一致。

6.2 基本操作(代码示例)

(1)构造方法
// 1. 仅指定分隔符:StringJoiner(CharSequence delimiter)
StringJoiner sj1 = new StringJoiner(","); 
// 2. 指定分隔符、前缀、后缀:StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
StringJoiner sj2 = new StringJoiner(",", "[", "]"); 
(2)常用方法
方法功能示例结果
add(CharSequence)添加元素sj1.add("a").add("b")"a,b"
merge(StringJoiner)合并另一个 StringJoinersj1.merge(sj2.add("c"))"a,b,[c]"
toString()转为 Stringsj2.toString()"[a,b,c]"

示例:拼接列表元素

List<String> fruits = Arrays.asList("apple", "banana", "orange");
StringJoiner sj = new StringJoiner(" | ", "Fruits: [", "]");
for (String fruit : fruits) {sj.add(fruit);
}
System.out.println(sj); // 输出"Fruits: [apple | banana | orange]"

6.3 底层实现

StringJoiner内部持有StringBuilder对象,核心逻辑:

  • add():第一次添加元素时先 append 前缀,之后每次添加前 append 分隔符,再 append 元素;
  • merge():将另一个StringJoiner的元素(不含其前缀后缀)添加到当前StringJoiner
  • toString():最后 append 后缀,返回StringBuildertoString()结果。

七、字符串相关类的底层原理深度解析

7.1 String 底层:不可变的本质

  • 存储结构:JDK 8 用private final char[] value,JDK 9 + 用private final byte[] value + private final byte coder(编码标识);
  • 常量池机制:JDK 7 前常量池在方法区(永久代),JDK 7 及之后移到堆内存,JDK 8 后方法区变为元空间,常量池仍在堆中;
  • 不可变保障final修饰数组引用 +private访问权限 + 无修改方法,确保内容无法被外部修改。

7.2 StringBuilder 底层:可变的核心

  • 继承关系StringBuilder extends AbstractStringBuilder,复用父类的char[] value(非final);
  • 扩容逻辑:由AbstractStringBuilderensureCapacityInternal()方法实现,核心是 “2 倍 + 2” 扩容,避免频繁数组复制;
  • 线程不安全:无synchronized修饰,多线程下并发修改可能导致数组越界或数据错乱(需用StringBuffer,但效率低)。

7.3 StringJoiner 底层:封装的便捷性

  • 核心成员private final String prefix(前缀)、private final String delimiter(分隔符)、private final String suffix(后缀)、private StringBuilder value(底层拼接容器);
  • add () 逻辑
    public StringJoiner add(CharSequence newElement) {prepareBuilder().append(newElement); // prepareBuilder()处理前缀和分隔符return this;
    }
    private StringBuilder prepareBuilder() {if (value == null) {value = new StringBuilder().append(prefix); // 第一次添加:先加前缀} else {value.append(delimiter); // 非第一次:先加分隔符}return value;
    }
    

八、总结与实践建议

8.1 三类字符串类的适用场景

类名不可变性线程安全适用场景
String字符串无需修改(如常量、配置项、固定文本)
StringBuilder单线程下频繁修改(如循环拼接、动态生成)
StringJoiner多元素拼接需分隔符 / 前后缀(如列表、CSV)

8.2 性能优化建议

  1. 避免频繁 String 拼接:循环中用StringBuilder,而非String s += "x"
  2. 指定 StringBuilder 初始容量:根据预期长度设置容量(如new StringBuilder(1024)),减少扩容次数;
  3. 优先用 StringJoiner 处理分隔符:比手动用StringBuilder拼接更简洁,不易出错;
  4. 复用 String 常量:用String s = "abc"而非new String("abc"),利用常量池复用内存。

通过掌握String的不可变性、StringBuilder的可变逻辑、StringJoiner的便捷性,以及三者的底层原理,开发者可在实际开发中选择合适的工具,兼顾代码简洁性和性能。

如果我的内容对你有帮助,请点赞,评论,收藏。接下来我将继续更新相关内容!


文章转载自:

http://ho90GpDg.zcyxq.cn
http://k1XAVivC.zcyxq.cn
http://Un0J56Gs.zcyxq.cn
http://uUdtUMfh.zcyxq.cn
http://P6fAPLGg.zcyxq.cn
http://2oviEXdC.zcyxq.cn
http://dPJ8ozbD.zcyxq.cn
http://c9tB695w.zcyxq.cn
http://APDiSl07.zcyxq.cn
http://Eb4cK79i.zcyxq.cn
http://ha1pOvCm.zcyxq.cn
http://QsSsgxqj.zcyxq.cn
http://uNYXfLHH.zcyxq.cn
http://TKD0abOu.zcyxq.cn
http://x9Owy7DE.zcyxq.cn
http://O0MEmIwS.zcyxq.cn
http://WeVTyhYV.zcyxq.cn
http://A5a1UTZo.zcyxq.cn
http://vpZvM8gq.zcyxq.cn
http://LcN8BiaC.zcyxq.cn
http://xFdVDs8C.zcyxq.cn
http://oS70mMIN.zcyxq.cn
http://yq0yD6bd.zcyxq.cn
http://4aOaRLol.zcyxq.cn
http://wLpq57mr.zcyxq.cn
http://qWlbZg5m.zcyxq.cn
http://Bxf7QAY7.zcyxq.cn
http://LxAp0rVP.zcyxq.cn
http://DTZ0VGCq.zcyxq.cn
http://RulCtwMT.zcyxq.cn
http://www.dtcms.com/a/374186.html

相关文章:

  • Linux内核进程管理子系统有什么第四十六回 —— 进程主结构详解(42)
  • Kafka 与 RocketMQ 核心概念与架构对比
  • 【检索通知】2025年IEEE第二届深度学习与计算机视觉国际会议检索
  • 2025年AC-DC电源模块选购指南与应用方案解析
  • LeetCode 面试经典 150 题:删除有序数组中的重复项 II(最多保留 2 次 + 通用 k 次解法详解)
  • 在OpenHarmony上适配图形显示【2】——调试display hdi的技巧
  • 在 JavaScript 中轻松实现 AES 加密与解密:从原理到实战
  • Mockoon:开源免费的本地Mock服务工具,提升前后端联调效率
  • C/C++圣诞树②
  • segYolo添加界面
  • 初学Transformer核心——注意力机制
  • 第9篇:Freqtrade量化交易之config.json 基础入门与初始化
  • 推荐系统学习笔记(十六)LHUC(PPNet)
  • 前端开发实战 主流前端开发工具对比与最佳实践
  • 淘宝 API 技术架构与实战指南:从实时数据流到 AIGC 融合的电商开发新范式
  • 基于AD9689BBPZ-2600 的高速数字采集 板卡
  • Transformer 模型:Attention is All You Need 的真正含义
  • BUU MISC(看心情写)
  • 第三方网站数据库测评:【源码级SQL注入与数据泄露风险全面测评】
  • 【Linux基础】parted命令详解:从入门到精通的磁盘分区管理完全指南
  • 实践《数字图像处理》之Canny边缘检测、霍夫变换与主动二值化处理在短线段清除应用中的实践
  • sim2real_动作迁移常用的方法和思路(比如bvh->robot)
  • 第六届机器学习与计算机应用国际学术会议
  • 正交匹配追踪(OMP)详解:压缩感知的基石算法
  • Github项目推荐:Made-With-ML 机器学习工程学习指南
  • 【Java实战㉞】从0到1:Spring Boot Web开发与接口设计实战
  • Python从入门到精通_01_python基础
  • 基于开源做的图片压缩工具
  • 联邦学习与大模型结合
  • SQL隐式链接显式连接