第007篇 String、StringBuilder 与 StringBuffer:拼接性能三选一

简介: Android面试中,String、StringBuilder与StringBuffer的选型本质是权衡:String不可变、线程安全但拼接低效;StringBuilder单线程高性能,扩容可控;StringBuffer加锁保障多线程安全,但有同步开销。真功夫在量化场景、预估容量、规避内存抖动。

Android 面试里,String、StringBuilder 与 StringBuffer 的性能问题最能试出工程功底:它没有标准答案,考的是"先量化、再归因、后验证"的思路。多数候选人败在张口就是结论——"String 慢,用 StringBuilder"——却说不清慢在哪、慢多少、什么时候其实不慢。把三者的区别从一个真实案例里讲透,比背十个概念得分高。

先把结论立住:三者解决的是不同问题

一句话:String 是不可变对象,每次"修改"都产生新对象;StringBuilder 是可变字符序列、非线程安全、速度快;StringBuffer 与 StringBuilder 接口一致,但方法加了 synchronized、线程安全、代价是并发下的同步开销。选型的关键不是"谁快",而是"要不要在线程间共享同一个可变序列"。

这里要先立住一个前提:StringBuilder 的"可变"指的是它内部持有可扩容的字符数组,append 大多在原数组上操作,不每次新建对象。这正是它比 String 拼接快的根本原因;而 StringBuffer 在此基础上多了一层锁,单线程下纯属多余的开销。

机制拆解:不可变、扩容与同步

String 不可变的根因在前一轮已经讲清:所有修改方法返回新对象,原对象冻结。StringBuilder 内部维护一个 char[] 和一个记录已用长度的 count,容量不够时按"旧容量 *2 + 2"扩容并拷贝。默认构造时容量为 16,如果预先知道大概长度,传入初始容量能少几次扩容拷贝。

StringBuffer 几乎逐方法加 synchronized,保证多线程同时 append 不会乱序或丢字符。但代价是:即使只有一个线程在用,每次调用也要走一遍锁的获取与释放。JVM 的偏向锁优化能缓解一部分,但比 StringBuilder 仍慢一截。所以结论很明确:单线程拼接用 StringBuilder,多线程共享同一个可变序列才考虑 StringBuffer——而后者在大多数业务代码里其实用不到,因为字符串拼接大多发生在单个请求或单条线程内。

代码里见真章

看一段能直接跑的代码,把上面的机制落到具体写法上:

// String / StringBuilder / StringBuffer 的取舍
String a = "hello";
String b = a + " world";        // 编译期会改成 StringBuilder.append,单次拼接无妨
System.out.println(a == b);     // false:产生新对象

// 循环里用 += 是反例:每次都新建 StringBuilder 再 toString
String slow = "";
for (int i = 0; i < 1000; i++) slow += i;   // 产生约 1000 个临时对象

// 正确:一次分配,反复 append
StringBuilder sb = new StringBuilder(64);    // 预给容量,少扩容
for (int i = 0; i < 1000; i++) sb.append(i);
String fast = sb.toString();                 // 只在最后生成一次 String

// 多线程共享才用 StringBuffer
StringBuffer sbuf = new StringBuffer();
sbuf.append("a").append("b");                // 方法加了 synchronized

这段代码值得盯三处:第一处,a + " world" 在单次拼接时编译器本就会优化成 StringBuilder,不必谈之色变;第二处,循环里的 += 才是真问题——每轮都新建再销毁,临时对象数量随循环次数线性增长;第三处,new StringBuilder(64) 预给容量,把扩容拷贝从"可能发生"变成"基本不发生",热路径上这是可观测的差别。

最常见的几个坑

最常见的坑是以为"用 StringBuilder 总比 String 快",于是在单条语句的少量拼接上也层层封装,反而让代码更绕。单次或常量拼接用 + 即可,编译器会处理好;只有循环、多次累积、或长度未知时才值得上 StringBuilder。选型看场景,不看口号。

其次是忽略 StringBuilder 的扩容代价:默认容量 16,追加内容远超时会发生多次翻倍拷贝。若能用 new StringBuilder(int) 给一个贴近实际的上限,扩容次数直接归零。这点在拼大报文、拼长 SQL 时尤其明显。

还有一个更隐蔽的坑:把 StringBuilder 当作线程安全的容器在多线程间共享。StringBuilder 非线程安全,并发 append 会丢字符或顺序错乱,这种情况该用 StringBuffer,或更好的做法是每个线程各自持有、最后合并。别用"单线程思路"去写并发代码。

再一个是用 String 的 intern() 去"优化"内存,却忽略了它把对象塞进常量池的代价与生命周期。大量 intern 在老 JDK 上曾引发永久代 OOM,现代 JDK 虽移到堆上,仍不该滥用。需要去重共享字符串,优先考虑显式的缓存 Map,而不是 blindly intern。

面试中的经典追问

"请简单介绍一下 String、StringBuilder 与 StringBuffer 的区别?"——一句话定位:String 不可变、StringBuilder 可变非线程安全、StringBuffer 可变且线程安全;一个场景佐证:循环拼接用 StringBuilder,单线程足矣;一个边界收尾:StringBuffer 的 synchronized 在单线程下是纯开销。按"结论—依据—边界"三步走最稳。

"StringBuilder 的扩容机制是什么?"——动机是"减少拷贝",机制是容量不够时按"旧容量 *2 + 2"分配新数组并 System.arraycopy,代价是扩容那一次的拷贝成本;所以给初始容量能从根上消掉大部分扩容。讲到这再补一句"默认 16",面试官基本就信你是真用过。

"StringBuffer 既然线程安全,是不是都比 StringBuilder 好?"——这题考的是权衡:线程安全只在"多线程共享同一个实例"时有意义,而字符串拼接大多发生在单线程内,此时 synchronized 纯属开销。真正的高并发场景往往让每个线程各持一个 StringBuilder 再合并,而不是共用一个 StringBuffer。

"你在项目里因为字符串踩过什么坑?"——行为题,拿真实案例说话。比如曾在循环里用 += 拼接日志导致某页卡顿,用 Profiler 抓到大量临时 String 分配,改成预容量 StringBuilder 后帧率回升;或者曾误把 StringBuilder 跨线程共享导致偶发错乱。讲"复现—定位—修复"的结构,比空谈概念有说服力得多。

工程落地:真实项目里怎么用

字符串在项目里无处不在,几个高频落点:一是日志、SQL、JSON 这类"循环或多段累积"场景一律用 new StringBuilder(估略容量),避免临时对象放大 GC;二是 DAO 层拼动态 SQL 时用 PreparedStatement 占位符替代字符串拼接,既防 SQL 注入又顺带解决拼接性能;三是把对外接口返回的字符串视为不可变,不在内部持有后去"期待"它被外部改动;四是把"比较内容用 equals、可能为 null 先判空"做成静态检查规则。

一个稳妥的工程约定:常量或单次拼接用 +,循环与未知长度累积用 StringBuilder 且尽量给初始容量,多线程共享可变序列才用 StringBuffer;把高频出现的字面量提成 static final 常量减少重复对象。把这几条固化,字符串相关的性能与并发坑能少一大半。

给正在准备面试的你

理解三者之后,需要在真实项目里练一遍:写一个循环用 += 拼接一千次,再用预容量 StringBuilder 写一遍,用 Profiler 或简单计时对比两者在对象分配上的差别;再亲手跑 new StringBuilder(16) 与默认构造的扩容次数对比。这两步做下来,比再读三遍资料都管用。

如果只记两句话,就记这两句:第一,循环或多段累积拼接用 StringBuilder 并尽量给初始容量,单次拼接用 + 编译器自己会优化;第二,StringBuffer 的 synchronized 只在多线程共享同一实例时才有价值,单线程下纯属开销。把这两句讲顺,字符串这一关就过了。


如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。你的支持就是这个系列持续更新的动力。

「Android 软件开发面试·从入门到精通」连载系列

上一篇:String-不可变性:为什么字符串要设计成不可变

下一篇预告:面向对象三大特性:封装、继承、多态怎么讲才透

有任何问题欢迎在评论区留言交流。

相关文章
|
17天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
8483 24
|
16天前
|
人工智能 并行计算 PyTorch
秋叶 ComfyUI 2026 整合包 v3.2 完整部署教程:Python 3.13 + Torch 2.13 全栈升级
秋叶aaaki ComfyUI 2026年8月整合包v3.2正式发布!全面升级Python 3.13.11、PyTorch 2.13.0+cu130及ComfyUI v0.30.2,原生支持MiniMax H3、Wan 2.2、Qwen-Image-2.1等2026主流音视频/图像模型,解压即用,无需环境配置。
2826 14
|
15天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
2021 4
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
14天前
|
人工智能 编解码 并行计算
MiniMax-H3 一键整合包技术文档:8G 显存运行 AI 漫剧制作 —— 角色替换 / 动作迁移 / 文图生视频部署与调参指南
MiniMax H3 是 MiniMax 开源的全模态视频生成模型,支持文/图/音/视多条件输入,输出最高2K、15秒带双声道音频视频。本文档详述其Int8量化版在8GB显存下的本地一键部署、三段式工作流(EDIT/REPLACE/CONTINUE)、参数调优及常见问题排查。(239字)
|
10天前
|
人工智能 Linux 开发者
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
Codex是OpenAI推出的AI编程智能体,可读取本地项目、理解需求并自动修改代码。支持桌面GUI、命令行(CLI)及VS Code/Cursor插件三种形态,覆盖可视化操作、终端高效开发与编辑器无缝集成场景,助开发者用自然语言驱动编码全流程。(239字)
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
|
4天前
|
人工智能 JSON Linux
【全网最详细】ComfyUI使用教程:下载+本地部署+配置+工作流搭建一篇搞定(2026最新版)
ComfyUI是一款免费开源的本地AI绘图工具,采用节点式工作流设计,支持文生图、图生图、局部重绘、放大、换脸等多种功能。可离线运行,依赖显卡加速,无需联网。支持自定义流程保存与分享,插件生态丰富,适合进阶用户。(239字)
|
10天前
|
人工智能 JSON 编解码
【2026最新版】ComfyUI本地部署教程,新手也能看懂!
ComfyUI是本地运行的AI绘画工具,采用节点式工作流设计:通过拖拽连接“加载模型”“提示词编码”“采样”“解码”等模块,实现高度可控的文生图。新手推荐使用秋叶整合包,一键启动、内置模型管理与插件安装器,轻松上手。(239字)

热门文章

最新文章