【Java 温故而知新系列】基础知识-06 深入理解String类
<p><span style="font-size: 15px">有编码经验的小伙伴一定知道,String类在实际编码过程中会经常使用到,但是你真的了解String类吗?</span></p><p><span style="font-size: 15px">先来看看几个常见的问题吧:</span></p>
<ul>
<li><span style="font-size: 15px">为什么String类是不可变,如此设计的目的?</span></li>
<li><span style="font-size: 15px">为什么有人说 String str2 = new String("Hello"); 会创建了2个对象?</span></li>
<li><span style="font-size: 15px">String, StringBuffer 和 StringBuilder的区别 ?</span></li>
<li><span style="font-size: 15px">为什么拼接字符串时不推荐使用“+”拼接?</span></li>
</ul>
<p><span style="font-size: 15px">带着这些问题,我们一起来深入学习一下String类吧。</span></p>
<h1>1、String类的定义</h1>
<p style="margin-left: 30px"><span style="font-size: 15px"><strong>String类是一个引用类型(Reference Type),它用于表示由字符组成的字符串。在Java中,字符串被视为一个对象而不是基本数据类型。</strong></span></p>
<p style="margin-left: 30px"><span style="font-size: 15px">不是基本类型这一点其实很好证明,直接去看一眼String类的源码就行,下图为源码截图(所有源码截图均为JDK1.8):</span></p>
<p style="margin-left: 30px"><img src="https://img2024.cnblogs.com/blog/494241/202501/494241-20250124095905869-1976027151.png"></p>
<p style="margin-left: 30px"><span style="font-size: 15px">从源码截图中可以看出String 类的声明,从源码里面看String里面实际上是用的一个 char 数组来存储的字符(JDK9+ 已改为 byte数组),</span></p>
<p style="margin-left: 30px"><span style="font-size: 15px">我觉得可以把String理解成 char 的另一高级包装类(这个说法仅个人看法,不一定准确)。其他包装类详情可见这篇 基础知识-03 基本类型对应之包装类 。</span></p>
<p style="margin-left: 30px"><span style="font-size: 15px">Java中的基本类型只有这8种(byte、short、int、long、float、 double、char、boolean)详情可见这篇 基础知识-02 数据基本类型。</span></p>
<h1>2、String类常用的方法</h1>
<table>
<thead>
<tr><th data-spm-anchor-id="5176.28103460.0.i40.178d1db8MF6Z0I">方法名</th><th>语法</th><th>功能描述</th><th>示例</th></tr>
</thead>
<tbody>
<tr>
<td data-spm-anchor-id="5176.28103460.0.i41.178d1db8MF6Z0I">length</td>
<td><code>int length()</code></td>
<td>返回字符串的长度(字符个数)</td>
<td><code>"Hello".length()</code> → <code>5</code></td>
</tr>
<tr>
<td>charAt</td>
<td><code>char charAt(int index)</code></td>
<td>返回指定索引位置的字符(索引从0开始)</td>
<td><code>"Hello".charAt(1)</code> → <code>'e'</code></td>
</tr>
<tr>
<td>concat</td>
<td><code>String concat(String str)</code></td>
<td>将指定字符串连接到当前字符串末尾</td>
<td><code>"Hi".concat("!")</code> → <code>"Hi!"</code></td>
</tr>
<tr>
<td>equals</td>
<td><code>boolean equals(Object anObject)</code></td>
<td>判断字符串是否与指定对象相等(区分大小写)</td>
<td><code>"abc".equals("ABC")</code> → <code>false</code></td>
</tr>
<tr>
<td>equalsIgnoreCase</td>
<td><code>boolean equalsIgnoreCase(String anotherString)</code></td>
<td>判断字符串是否相等,忽略大小写</td>
<td><code>"abc".equalsIgnoreCase("ABC")</code> → <code>true</code></td>
</tr>
<tr>
<td>indexOf</td>
<td><code>int indexOf(int ch)</code><br><code>int indexOf(String str)</code></td>
<td>返回指定字符或子串第一次出现的索引,未找到返回 -1</td>
<td><code>"hello".indexOf('l')</code> → <code>2</code></td>
</tr>
<tr>
<td>lastIndexOf</td>
<td><code>int lastIndexOf(int ch)</code><br><code>int lastIndexOf(String str)</code></td>
<td>返回指定字符或子串最后一次出现的索引</td>
<td><code>"hello".lastIndexOf('l')</code> → <code>3</code></td>
</tr>
<tr>
<td>substring</td>
<td><code>String substring(int beginIndex)</code><br><code>String substring(int beginIndex, int endIndex)</code></td>
<td>截取子字符串(左闭右开)</td>
<td><code>"Hello".substring(1, 4)</code> → <code>"ell"</code></td>
</tr>
<tr>
<td>replace</td>
<td><code>String replace(char oldChar, char newChar)</code><br><code>String replace(CharSequence target, CharSequence replacement)</code></td>
<td>替换所有匹配的字符或子串</td>
<td><code>"hello".replace('l', 'x')</code> → <code>"hexxo"</code></td>
</tr>
<tr>
<td>toLowerCase</td>
<td><code>String toLowerCase()</code></td>
<td>将字符串转换为小写</td>
<td><code>"HELLO".toLowerCase()</code> → <code>"hello"</code></td>
</tr>
<tr>
<td>toUpperCase</td>
<td><code>String toUpperCase()</code></td>
<td>将字符串转换为大写</td>
<td><code>"hello".toUpperCase()</code> → <code>"HELLO"</code></td>
</tr>
<tr>
<td>trim</td>
<td><code>String trim()</code></td>
<td>去除字符串两端的空白字符(空格、制表符等)</td>
<td><code>" hi ".trim()</code> → <code>"hi"</code></td>
</tr>
<tr>
<td>split</td>
<td><code>String[] split(String regex)</code></td>
<td>根据正则表达式分割字符串,返回字符串数组</td>
<td><code>"a,b,c".split(",")</code> → <code>["a", "b", "c"]</code></td>
</tr>
<tr>
<td>startsWith</td>
<td><code>boolean startsWith(String prefix)</code></td>
<td>判断字符串是否以指定前缀开头</td>
<td><code>"Hello".startsWith("He")</code> → <code>true</code></td>
</tr>
<tr>
<td>endsWith</td>
<td><code>boolean endsWith(String suffix)</code></td>
<td>判断字符串是否以指定后缀结尾</td>
<td><code>"Hello".endsWith("lo")</code> → <code>true</code></td>
</tr>
<tr>
<td>contains</td>
<td><code>boolean contains(CharSequence s)</code></td>
<td>判断字符串是否包含指定的字符序列</td>
<td><code>"Hello".contains("ell")</code> → <code>true</code></td>
</tr>
<tr>
<td>compareTo</td>
<td><code>int compareTo(String anotherString)</code></td>
<td>按字典顺序比较两个字符串(返回整数)</td>
<td><code>"apple".compareTo("banana")</code> → 负数</td>
</tr>
<tr>
<td>join(静态)</td>
<td><code>String join(CharSequence delimiter, CharSequence... elements)</code></td>
<td>使用分隔符连接多个字符串</td>
<td data-spm-anchor-id="5176.28103460.0.i42.178d1db8MF6Z0I"><code>String.join("-", "a", "b", "c")</code> → <code>"a-b-c"</code></td>
</tr>
</tbody>
</table>
<h1>3、String为什么是不可变的?</h1>
<p style="margin-left: 30px"><span style="font-size: 15px">先来看看String源码:</span></p>
<p style="margin-left: 30px"><img src="https://img2024.cnblogs.com/blog/494241/202508/494241-20250806135105490-1820378359.png"></p>
<p style="margin-left: 30px"><span style="font-size: 15px">通过截图中红框我们可以知道,String类保存字符串的value 数组是一个由 private final 修饰的变量,有此可知String 对象被创建之后它的内容就不能再被修改。</span></p>
<p style="margin-left: 30px"><span style="font-size: 15px">我们再来看看String类的replace方法(方法有点多,仅以replace举例):</span></p>
<p style="margin-left: 30px"><img src="https://img2024.cnblogs.com/blog/494241/202508/494241-20250806150431084-926293387.png"></p>
<p> <span style="font-size: 15px">由截图源码可知String类的replace方法的返回新new的一个String对象。</span></p>
<p><span style="font-size: 15px"> <strong>所有看起来像是对字符串进行修改的操作实际上都会返回一个新的 String 对象,而原来的对象保持不变</strong>。</span></p>
<p> <span style="font-size: 15px"> 为什么String类要设计为不可变呢?</span></p>
<p><span style="font-size: 15px"> 关于这个问题,或许只有设计者才能最精准地阐释当初的设计初衷。不过结合 Java 的特性与实际应用场景,我个人比较认同以下几点原因,也欢迎大家补充其他见解:</span></p>
<ol data-spm-anchor-id="5176.28103460.0.i46.178d1db8MF6Z0I">
<li>
<p><span data-spm-anchor-id="5176.28103460.0.i45.178d1db8MF6Z0I">安全性:</span></p>
<ul>
<li data-spm-anchor-id="5176.28103460.0.i48.178d1db8MF6Z0I">不可变对象天然适合于多线程环境,因为它们的状态不能被改变,所以在并发访问时不需要考虑同步问题。这减少了并发编程中的复杂性和潜在的错误。</li>
<li>在涉及安全敏感的应用程序中,如网络连接、密码处理等,不可变字符串可以防止数据被意外或恶意地修改。</li>
</ul>
</li>
<li>
<p>简化编程模型:</p>
<ul>
<li>由于不可变性,任何对字符串的操作都会返回一个新的字符串实例,而不是修改原始实例。这种行为使得理解和预测代码的行为更加简单和直观。</li>
</ul>
</li>
<li>
<p>哈希表的关键支持:</p>
<ul>
<li>String 常被用作 HashMap、HashSet 等集合的键(Key)。哈希表的工作依赖于键的哈希值稳定性 —— 如果 String 是可变的,修改其内容会导致哈希值变化,这会破坏哈希表的存储结构,导致无法正确查询、删除元素。不可变性保证了字符串的哈希值在创建后永不改变,使其成为可靠的哈希键。</li>
</ul>
</li>
<li>
<p>字符串常量池的基础:</p>
<ul>
<li>字符串常量池(String Pool)的核心是复用相同内容的字符串以节省内存。如果 String 是可变的,当一个字符串被修改时,所有引用它的变量都会受到影响,常量池的复用机制就会失效。不可变性保证了常量池中的字符串一旦创建就不会被修改,确保了复用的安全性。</li>
</ul>
</li>
<li>
<p><span style="font-size: 15px">安全性与持久化:</span></p>
<ul>
<li data-spm-anchor-id="5176.28103460.0.i47.178d1db8MF6Z0I"><span style="font-size: 15px">在某些情况下,比如序列化或在网络上传输对象时,不可变对象更容易管理和确保一致性,因为你不用担心对象状态的变化。</span></li>
</ul>
</li>
</ol>
<h1>4、String Pool - <strong>字符串常量池</strong></h1>
<p> <span style="font-size: 15px">我们先来看看定义:Java中的字符串常量池(String Pool,也称为字符串池)是一种特殊的内存区域,用于存储字符串常量。也是 JVM 为了优化字符串使用而设计的一种内存结构,用于存储字符串字面量并实现复用,核心目的是减少内存消耗和提高性能。 </span></p>
<h3 class="header-vfC6AV auto-hide-last-sibling-br"> 核心特性</h3>
<ol class="auto-hide-last-sibling-br"><ol class="auto-hide-last-sibling-br">
<li>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space"><span style="font-size: 15px">存储内容</span><br class="container-utlnW2 wrapper-d0Cc1k undefined"><span style="font-size: 15px">主要存储字符串字面量(如 <code>"abc"</code>)和通过 <code>intern()</code> 方法加入的字符串对象引用(JDK 7+)。</span></div>
</li>
<li>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space">位置</div>
<ul class="auto-hide-last-sibling-br">
<li>JDK 6 及之前:位于永久代(PermGen)。</li>
<li>JDK 7 及之后:迁移至堆内存(Heap),更便于垃圾回收。</li>
</ul>
</li>
<li>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space"><span style="font-size: 15px">复用机制</span><br class="container-utlnW2 wrapper-d0Cc1k undefined"><span style="font-size: 15px">相同内容的字符串在常量池中只会保存一份,多个引用可以共享这个实例,避免重复创建相同内容的字符串对象。<strong> </strong></span></div>
</li>
</ol></ol>
<h3 class="header-vfC6AV auto-hide-last-sibling-br"> 工作原理:<strong> </strong></h3>
<ol class="auto-hide-last-sibling-br">
<li style="list-style-type: none"><ol>
<li>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space"><span style="font-size: 15px">字面量加载</span><br class="container-utlnW2 wrapper-d0Cc1k undefined"><span style="font-size: 15px">当 JVM 加载类时,会经历加载、验证、准备、解析、初始化等阶段。在解析阶段,JVM 会对 class 文件中<code>constant_pool</code>表的符号引用进行解析,将其中的字符串字面量(符号引用)转换为直接引用。</span><br class="container-utlnW2 wrapper-d0Cc1k undefined"><span style="font-size: 15px">此时,JVM 会检查字符串常量池:</span>
<ul class="auto-hide-last-sibling-br">
<li><span style="font-size: 15px">若池中已存在相同内容的字符串,则直接复用其引用;</span></li>
<li><span style="font-size: 15px">若不存在,则在常量池中创建该字符串对象,并将引用存入池中。</span></li>
</ul>
<span style="font-size: 15px">
何为字符串字面量?如 <code>String s = "abc"; 或者 String s = new String("abc")</code>,JVM 会先检查常量池:</span></div>
<ol>
<li><span style="font-size: 15px">若已存在 <code>"abc"</code>,则直接让 <code>s</code> 指向常量池中的该对象。</span></li>
<li><span style="font-size: 15px">若不存在,则在常量池创建 <code>"abc"</code> 并让 <code>s</code> 指向它。</span></li>
<li><span style="font-size: 15px">对于通过<code>new String("abc")</code>创建的对象,其中的<code>"abc"</code>字面量仍会在类加载的解析阶段进入常量池,而<code>new</code>操作只是在堆中创建一个新的字符串对象(复制常量池中的内容)。</span></li>
</ol></li>
<li>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space"><span style="font-size: 15px"><code>intern()</code> 方法的作用</span><br class="container-utlnW2 wrapper-d0Cc1k undefined"><span style="font-size: 15px">对于通过 <code>new String(...)</code> 创建的堆中字符串对象,调用 <code>intern()</code> 会:</span></div>
<ol>
<li><span style="font-size: 15px">检查常量池是否存在相同内容的字符串。</span></li>
<li><span style="font-size: 15px">若存在,返回常量池中的引用。</span></li>
<li><span style="font-size: 15px"><strong>若不存在,将当前堆对象的引用存入常量池(JDK 7+),并返回该引用</strong>。</span></li>
</ol></li>
</ol></li>
</ol>
<h3> 验证时刻: </h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> fun0() {
</span><span style="color: rgba(0, 128, 128, 1)">2</span> String s1 = "hello string pool"; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 字面量 "hello string pool" 加入常量池</span>
<span style="color: rgba(0, 128, 128, 1)">3</span> String s2 = "hello" + <span style="color: rgba(0, 0, 255, 1)">new</span> String(" string pool"); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 堆中创建新对象 "helo string pool"</span>
<span style="color: rgba(0, 128, 128, 1)">4</span> String s3 = s2.intern(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 常量池中已有,返回常量池中的引用</span>
<span style="color: rgba(0, 128, 128, 1)">5</span> System.out.println(s1 == s2); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> falses1指向常量池中的 "hello string pool",s2指向堆中的 "hello string pool"</span>
<span style="color: rgba(0, 128, 128, 1)">6</span> System.out.println(s1 == s3); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> true s1指向常量池中的 "hello string pool",s3 也指向常量池中的 "hello string pool";</span>
<span style="color: rgba(0, 128, 128, 1)">7</span> }</pre>
</div>
<p> </p>
<p>执行结果:</p>
<p style="margin-left: 30px"><img src="https://img2024.cnblogs.com/blog/494241/202508/494241-20250806161427558-2099572221.png"></p>
<p> </p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> fun1() {
</span><span style="color: rgba(0, 128, 128, 1)">2</span> String s = <span style="color: rgba(0, 0, 255, 1)">new</span> String("hello") + <span style="color: rgba(0, 0, 255, 1)">new</span> String(" string pool"); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 堆中创建新对象 "hello string pool"(常量池尚无 "hello string pool")</span>
<span style="color: rgba(0, 128, 128, 1)">3</span> String s2 = s.intern();<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 常量池存入 s 的引用,返回 s 本身</span>
<span style="color: rgba(0, 128, 128, 1)">4</span> System.out.println(s == s2); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> true(s 和 s2 指向同一个堆对象)</span>
<span style="color: rgba(0, 128, 128, 1)">5</span> System.out.println(s == "hello string pool"); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> true("hello string pool" 字面量现在指向常量池中的 s 引用)</span>
<span style="color: rgba(0, 128, 128, 1)">6</span> }</pre>
</div>
<p> </p>
<p>执行结果:</p>
<p style="margin-left: 30px"><img src="https://img2024.cnblogs.com/blog/494241/202508/494241-20250806162020861-507156852.png"></p>
<p><span style="font-size: 15px">通过上面2个例子的运行结果证明了字符串常量池工作原理跟我们前面的描述是一致的。</span></p>
<p><span style="font-size: 15px">读到这里,相信大家对开头的问题“为什么有人说<code>String str2 = new String("Hello");</code>会创建 2 个对象?”已经有了更准确的理解。</span></p>
<p style="margin-left: 30px"><span style="font-size: 15px">准确讲应该是:</span></p>
<ol class="auto-hide-last-sibling-br">
<li>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space">如果常量池中没有 "Hello"</div>
<ol class="auto-hide-last-sibling-br">
<li>第一步:类加载时,字符串字面量 <code>"Hello"</code> 会被加载到字符串常量池,创建 1 个常量池对象。</li>
<li>第二步:<code>new String(...)</code> 会在堆内存中创建 1 个新的字符串对象,该对象的内容是常量池中 <code>"Hello"</code> 的副本。<br class="container-utlnW2 wrapper-d0Cc1k undefined">此时总共创建 2 个对象(1 个常量池对象 + 1 个堆对象)。</li>
</ol></li>
<li>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space"><span style="font-size: 15px">如果常量池中已有 "Hello"</span></div>
<ol class="auto-hide-last-sibling-br">
<li><span style="font-size: 15px">由于常量池中的对象可复用,<code>new String(...)</code> 只会在堆内存中创建 1 个新的字符串对象。</span><br class="container-utlnW2 wrapper-d0Cc1k undefined"><span style="font-size: 15px">此时总共创建 1 个对象(仅堆对象)。 </span></li>
</ol></li>
</ol>
<h1>5、<strong>String, StringBuffer 和 StringBuilder区别</strong></h1>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space" style="margin-left: 30px"><span style="font-size: 15px">Java 中的 <code>String</code>、<code>StringBuffer</code> 和 <code>StringBuilder</code> 都是用于处理字符串的类,但它们在可变性、线程安全和性能上有显著区别,主要差异如下:</span></div>
<h3 class="header-vfC6AV auto-hide-last-sibling-br"> 可变性</h3>
<ul class="auto-hide-last-sibling-br">
<li style="list-style-type: none">
<ul>
<li>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space"><span style="font-size: 15px"><code>String</code>:不可变(Immutable)</span><br class="container-utlnW2 wrapper-d0Cc1k undefined"><span style="font-size: 15px">字符串一旦创建,其内容无法修改。任何修改操作(如拼接、替换)都会创建新的 <code>String</code> 对象,原对象保持不变。</span><br class="container-utlnW2 wrapper-d0Cc1k undefined"><span style="font-size: 15px">例:<code>s = s + "a"</code> 会生成新对象,而非修改原字符串。</span></div>
</li>
<li>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space"><span style="font-size: 15px"><code>StringBuffer</code> 和 <code>StringBuilder</code>:可变(Mutable)</span><br class="container-utlnW2 wrapper-d0Cc1k undefined"><span style="font-size: 15px">内部通过可动态扩容的字符数组存储内容,修改操作(如 <code>append()</code>、<code>insert()</code>)直接在原有数组上进行,不会创建新对象(除非需要扩容)。</span></div>
</li>
</ul>
</li>
</ul>
<h3 class="header-vfC6AV auto-hide-last-sibling-br"> 线程安全</h3>
<ul class="auto-hide-last-sibling-br">
<ul class="auto-hide-last-sibling-br">
<li>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space"><span style="font-size: 15px"><code>String</code>:天然线程安全</span><br class="container-utlnW2 wrapper-d0Cc1k undefined"><span style="font-size: 15px">由于不可变性,多线程并发访问时不会出现数据不一致问题,无需同步机制。</span></div>
</li>
<li>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space"><span style="font-size: 15px"><code>StringBuffer</code>:线程安全</span><br class="container-utlnW2 wrapper-d0Cc1k undefined"><span style="font-size: 15px">所有方法都被 <code>synchronized</code> 修饰,保证多线程环境下的操作原子性,但会带来额外的性能开销。</span></div>
</li>
<li>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space"><span style="font-size: 15px"><code>StringBuilder</code>:非线程安全</span><br class="container-utlnW2 wrapper-d0Cc1k undefined"><span style="font-size: 15px">未实现同步机制,性能优于 <code>StringBuffer</code>,但多线程并发修改可能导致数据错乱。 </span></div>
</li>
</ul>
</ul>
<h3 class="header-vfC6AV auto-hide-last-sibling-br"> 性能</h3>
<ul class="auto-hide-last-sibling-br">
<ul class="auto-hide-last-sibling-br">
<li>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space"><span style="font-size: 15px"><code>String</code>:性能最差</span><br class="container-utlnW2 wrapper-d0Cc1k undefined"><span style="font-size: 15px">频繁修改(如循环拼接)会产生大量临时对象,增加 GC 负担,效率低下。</span></div>
</li>
<li>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space"><span style="font-size: 15px"><code>StringBuilder</code>:性能最优</span><br class="container-utlnW2 wrapper-d0Cc1k undefined"><span style="font-size: 15px">无同步开销,单线程场景下拼接效率最高。</span></div>
</li>
<li>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space"><span style="font-size: 15px"><code>StringBuffer</code>:性能次之</span><br class="container-utlnW2 wrapper-d0Cc1k undefined"><span style="font-size: 15px">因同步锁的存在,性能略低于 <code>StringBuilder</code>,但高于 <code>String</code>。</span></div>
</li>
</ul>
</ul>
<h1>6、<strong>字符串拼接为什么不推荐“ + ”</strong></h1>
<p style="margin-left: 30px"><span style="font-size: 15px"><span style="color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif">在 Java 中,字符串拼接不推荐使用 </span><code style="outline: none; -webkit-font-smoothing: antialiased; box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; background: var(--color-inline-code-background); border-radius: 4px; color: rgba(0, 0, 0, 0.85); font-size: 16px; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; overflow-anchor: auto; line-height: var(--md-box-samantha-normal-text-line-height)">+</code><span style="color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif"> 运算符,核心原因与 </span><code style="outline: none; -webkit-font-smoothing: antialiased; box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; background: var(--color-inline-code-background); border-radius: 4px; color: rgba(0, 0, 0, 0.85); font-size: 16px; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; overflow-anchor: auto; line-height: var(--md-box-samantha-normal-text-line-height)">String</code><span style="color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif"> 的</span><span style="outline: none; -webkit-font-smoothing: antialiased; box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; font-weight: 600; line-height: var(--md-box-samantha-normal-text-line-height); overflow-anchor: auto; font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; color: var(--md-box-samantha-deep-text-color) !important">不可变性</span><span style="color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif">以及 </span><code style="outline: none; -webkit-font-smoothing: antialiased; box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; background: var(--color-inline-code-background); border-radius: 4px; color: rgba(0, 0, 0, 0.85); font-size: 16px; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; overflow-anchor: auto; line-height: var(--md-box-samantha-normal-text-line-height)">+</code><span style="color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif"> 运算符的</span><span style="outline: none; -webkit-font-smoothing: antialiased; box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; font-weight: 600; line-height: var(--md-box-samantha-normal-text-line-height); overflow-anchor: auto; font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; color: var(--md-box-samantha-deep-text-color) !important">底层实现机制</span><span style="color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif">直接相关,具体可以从以下角度理解:</span><span style="color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif"> </span> </span></p>
<h3 class="header-vfC6AV auto-hide-last-sibling-br"> String不可变性导致的临时对象爆炸 </h3>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space" style="outline: none; -webkit-font-smoothing: antialiased; box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; line-height: var(--md-box-samantha-normal-text-line-height); font-size: 16px; overflow-anchor: auto; font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; color: var(--md-box-samantha-normal-text-color) !important; margin-left: 30px"><span style="font-size: 15px"><code style="outline: none; -webkit-font-smoothing: antialiased; box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; background: var(--color-inline-code-background); border-radius: 4px; font-size: var(--md-box-samantha-normal-text-font-size); font-family: Menlo, Monaco, Consolas, "Courier New", monospace; overflow-anchor: auto; line-height: var(--md-box-samantha-normal-text-line-height); color: var(--md-box-samantha-normal-text-color) !important">String</code> 是不可变对象(内容一旦创建就无法修改)。</span></div>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space" style="outline: none; -webkit-font-smoothing: antialiased; box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; line-height: var(--md-box-samantha-normal-text-line-height); font-size: 16px; overflow-anchor: auto; font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; color: var(--md-box-samantha-normal-text-color) !important; margin-left: 30px"><span style="font-size: 15px">每次使用 <code style="outline: none; -webkit-font-smoothing: antialiased; box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; background: var(--color-inline-code-background); border-radius: 4px; font-size: var(--md-box-samantha-normal-text-font-size); font-family: Menlo, Monaco, Consolas, "Courier New", monospace; overflow-anchor: auto; line-height: var(--md-box-samantha-normal-text-line-height); color: var(--md-box-samantha-normal-text-color) !important">+</code> 拼接字符串时,JVM 都必须创建一个<span style="outline: none; -webkit-font-smoothing: antialiased; box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; font-weight: 600; line-height: var(--md-box-samantha-normal-text-line-height); overflow-anchor: auto; color: var(--md-box-samantha-deep-text-color) !important">新的 <code>String</code> 对象</span>来存储拼接结果,</span></div>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space" style="outline: none; -webkit-font-smoothing: antialiased; box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; line-height: var(--md-box-samantha-normal-text-line-height); font-size: 16px; overflow-anchor: auto; font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; color: var(--md-box-samantha-normal-text-color) !important; margin-left: 30px"><span style="font-size: 15px">而原来的字符串对象则会变成垃圾等待回收。</span></div>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space" style="outline: none; -webkit-font-smoothing: antialiased; box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; line-height: var(--md-box-samantha-normal-text-line-height); font-size: 16px; overflow-anchor: auto; font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; color: var(--md-box-samantha-normal-text-color) !important; margin-left: 30px"> </div>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space" style="outline: none; -webkit-font-smoothing: antialiased; box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; line-height: var(--md-box-samantha-normal-text-line-height); font-size: 16px; overflow-anchor: auto; font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; color: var(--md-box-samantha-normal-text-color) !important; margin-left: 30px">举例:<span style="font-family: "Courier New"; font-size: 12px; background-color: rgba(245, 245, 245, 1); font-style: italic"><br></span></div>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space" style="outline: none; -webkit-font-smoothing: antialiased; box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; line-height: var(--md-box-samantha-normal-text-line-height); font-size: 16px; overflow-anchor: auto; font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; color: var(--md-box-samantha-normal-text-color) !important">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> String s = "a"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">2</span> s += "b";<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建新对象 "ab",原 "a" 成为垃圾</span>
<span style="color: rgba(0, 128, 128, 1)">3</span> s += "c";<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 再创建新对象 "abc",原 "ab" 成为垃圾</span></pre>
</div>
<p style="margin-left: 30px"><span style="font-size: 15px"> <span style="color: var(--md-box-samantha-normal-text-color)">这种操作在循环中会被放大:如果循环拼接 1000 次,会产生近 1000 个临时对象,不仅占用大量内存,还会触发频繁的垃圾回收(GC),严重影响性能。</span><span style="color: var(--md-box-samantha-normal-text-color)"> </span></span></p>
</div>
<h3 class="header-vfC6AV auto-hide-last-sibling-br"> <code>“+”</code> 运算符</h3>
<p class="header-vfC6AV auto-hide-last-sibling-br" style="margin-left: 30px"><span style="font-size: 15px">编译时会将“ <code>+</code> ”转换为 <code>StringBuilder</code> 的 <code>append()</code> 操作。但这种优化仅适用于简单场景,在循环中会失效。</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> String result = ""<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i < 1000; i++<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">3</span> result += i;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 编译器无法感知循环边界</span>
<span style="color: rgba(0, 128, 128, 1)">4</span> }</pre>
</div>
<p style="margin-left: 30px"><span style="font-size: 15px"> 编译器会被循环 “误导”,在每次迭代中创建新的 <code>StringBuilder</code> 对象(而非复用一个),相当于执行:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 循环内的等效操作(低效)</span>
<span style="color: rgba(0, 128, 128, 1)">2</span> result = <span style="color: rgba(0, 0, 255, 1)">new</span> StringBuilder(result).append(i).toString(); </pre>
</div>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space">
<h3 class="header-vfC6AV auto-hide-last-sibling-br"> <span style="font-family: monospace">结论:</span></h3>
</div>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space" style="margin-left: 30px"><span style="font-size: 15px"><code>+</code> 运算符仅适合编译期可确定的简单拼接(如 <code>"hello" + "world"</code> 会被编译器直接优化为 <code>"helloworld"</code>),但对于动态拼接(尤其是循环中的拼接),<code>+</code> 会导致大量临时对象创建和 GC 开销,性能极差。 </span></div>
<div class="auto-hide-last-sibling-br paragraph-JOTKXA paragraph-element br-paragraph-space" style="margin-left: 30px"><span style="font-size: 15px">因此,实际开发中应优先使用 <code>StringBuilder</code>(单线程)或 <code>StringBuffer</code>(多线程),而非 <code>+</code> 运算符。</span></div>
<p style="margin-left: 30px"> </p>
<p style="margin-left: 30px"> </p>
<p> </p><br><br>
来源:https://www.cnblogs.com/zhulu/p/18689055
頁:
[1]