news 2026/9/5 19:02:37

快手·Android 开发面试——StringBuilder 和 StringBuffer 选错一个

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
快手·Android 开发面试——StringBuilder 和 StringBuffer 选错一个

快手客户端性能组面试有个习惯:不考你多炫的架构,先问字符串拼接。

"App 里有个接口要拼上千条日志,你用+还是 StringBuilder?"——看似送分,答错的人能有一半。

说白了,String、StringBuilder、StringBuffer 三兄弟,初级岗背定义,快手要的是你真的在性能敏感路径上趟过坑。今天把底层结构、性能差异、编译器优化一次性讲清。

一、三者底层到底差在哪

面试官:String、StringBuilder、StringBuffer 区别?

候选人(标准答法):String 不可变,后两个可变;StringBuffer 线程安全,StringBuilder 不是。

深度解析:从存储结构看就明白了。

  • String:Java 8 是private final char value[],Java 9+ 是byte[](紧凑字符串)。不可变,任何"改"都返回新对象。
  • StringBuilder / StringBuffer:内部都是一个可变的 char[] 缓冲区,默认容量 16。append()往里填,不够就扩容。区别只在——StringBuffer 的每个 public 方法都加了synchronized,StringBuilder 没有。
// AbstractStringBuilder 的共同底层 char[] value; // 缓冲区 int count; // 已用长度

普通答法 vs 高分答法

  • 普通:StringBuffer 线程安全,所以慢;StringBuilder 快。
  • 高分:我会点出"线程安全"的代价是每次方法调用都要拿锁(即使单线程也在空转同步),所以在明确单线程的场景,StringBuffer 是纯粹的性能浪费。快手这种高并发客户端,主线程拼字符串用 StringBuilder,不要无脑 StringBuffer。

有意思的是,很多人不知道:StringBuffer 的toString()在旧 JDK 也是synchronized的,新版本优化掉了,但方法级的锁开销仍在。

二、为什么 += 拼接会慢(性能真相)

本篇第二道核心问答,也是这道题的灵魂。

面试官:那s += "x"到底慢在哪?

深度解析:关键看是不是在循环里。

单条语句String s = a + b + c;,编译器会优化(见第三组),几乎没额外开销。但循环里的+=是灾难:

var s = "" for (i in 0 until 10000) { s += i // 每一轮都发生什么? }

展开看每一轮实际干的事:

1. 新建一个StringBuilder

2.appends的内容;

3.append当前i

4. 调toString()生成新的 String

5. 旧的s变成垃圾。

也就是说,第 n 轮要复制前 n-1 轮的所有字符。总复制量是 1+2+...+n ≈O(n²)。1 万次循环,临时对象数以万计,GC 直接起飞。

字节码视角s += i编译后大致是new StringBuilder().append(s).append(i).toString(),每次循环都new一次。这就是为什么慢。

普通答法 vs 高分答法

  • 普通:因为 String 不可变,每次拼接都新建对象,所以慢。
  • 高分:我会把"O(n²) 复制量"和"每轮 new StringBuilder + 产生中间 String 垃圾"讲清楚,并对比正确写法只 new 一次、线性复制。量化之后,考官一眼知道你真测过。

三、编译器到底做了什么优化

本篇第三道核心问答,很多人把它和"慢"混为一谈。

面试官:那编译器对+拼接不是有优化吗?

深度解析:有,但只覆盖单条语句

javac在编译期会把同一个表达式里的+串,自动转成StringBuilder.append的链式调用:

// 源码 String s = a + b + c; // 编译后等价于 String s = new StringBuilder().append(a).append(b).append(c).toString();

注意:这里只 new一次StringBuilder,所以单条语句的+性能没问题,别被"String 慢"的谣言吓到。

但循环里每次迭代是独立语句,编译器没法把跨迭代的拼接合并,于是每轮各 new 一次——优化在此失效。

Android 侧补充:R8 / ProGuard 在编译期还会做"字符串常量折叠","a"+"b"直接变成"ab";Kotlin 编译器对字符串模板"$a$b"也是生成 append 链。但没有任何编译器能跨循环合并,这是语义决定的。

高分答法:我会总结一句——"优化救得了单条语句,救不了循环"。判断用不用 StringBuilder,看的是"拼接是否跨多次迭代",而不是"用了几个加号"。

四、容量与扩容:真正的性能细节

面试官:那 StringBuilder 就一定快?不注意容量也白搭吧。

深度解析:对。默认容量只有16,append 超出时会扩容。看AbstractStringBuilder的扩容逻辑:

int newCapacity = (oldCapacity << 1) + 2; // 旧容量 *2 + 2 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; value = Arrays.copyOf(value, newCapacity); // 复制旧数组到新数组

意思是:超了就翻倍 +2,然后Arrays.copyOf把老数据整体拷过去。频繁扩容 = 频繁数组拷贝。

实战场景:快手客户端里拼一段接口返回的 JSON 日志,长度可能上千字符。如果你new StringBuilder()默认 16,会经历 16→34→70→142→286→574→1150 多次扩容拷贝。正确做法:

// 预估容量,一步到位,零扩容 val sb = StringBuilder(estimatedLen) for (item in list) sb.append(item.toLogLine())

高分答法:我会补一个 Android 专属点——TextView.setText(CharSequence)内部大量用 Spannable/StringBuilder,如果你在onBindViewHolder里反复拼长文本且不设容量,列表滑动就会掉帧。这类细节,性能组考官最爱听。

五、开放追问:多线程到底用谁

面试官:多线程环境拼字符串,该用 StringBuffer 吗?

深度解析:这题有陷阱。StringBuffer 确实线程安全,但线程安全≠该用

现代写法更推荐:

  • 局部变量:每个线程自己的 StringBuilder,根本不需要锁,最快。
  • ThreadLocal:复用缓冲区又避锁。
  • StringJoiner(Java 8+):专门拼集合,带分隔符:
val j = StringJoiner(",") list.forEach { j.add(it) } val s = j.toString() // a,b,c
  • Kotlin 的buildString {}:内部就是StringBuilder,DSL 写法更爽:
val s = buildString { repeat(1000) { append(it) } }

高分答法:我的看法是——除非你要在一个被多线程共享的同一缓冲区上并发 append(这种场景极少),否则 StringBuffer 基本是历史包袱。与其加锁不如让每个线程各拼各的、最后合并。这展现的不是 API 记忆,是并发设计意识。

收尾:几条能落地的建议

三兄弟这道题,快手想筛的是"有没有性能体感"。String 不可变不是缺点,是特性;+不是原罪,循环里才是;StringBuilder 不是万能,不设容量照样拉胯。

面试 Tips(具体答法)

  • 先讲底层结构(char[] 缓冲区 + 默认容量16),再讲差异,逻辑最顺。
  • 被问+慢,立刻分"单条语句(编译器优化)vs 循环(O(n²))"两情况,别一刀切。
  • 提扩容<<1 +2Arrays.copyOf拷贝,展示你看过源码。
  • 多线程题,反手给 ThreadLocal / StringJoiner / buildString,区分度拉满。

讲真,客户端性能优化八成都在这些"不起眼"的细节里。

---

点赞、在看、转发,三连是对我最大的支持!

「Android 大厂面经·从入门到精通」连载系列

上一篇:第002篇 字节跳动·应届 Android 面试——String 为什么设计成不可变

下一篇预告:第004篇 百度·初级 Android 面试——equals 和 hashCode 的约定

评论区聊聊:你面试遇到过最难的 Android 问题是哪道?

关于本系列:「Android 大厂面经·从入门到精通」是360篇连载系列,覆盖美团、字节跳动、阿里巴巴、快手、百度、京东、华为、小米等30+家公司从 Java 基础到架构师终面的真实面试内容。本篇属于阶段1·入门篇——Java 语言基础。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 19:02:10

SpringBoot+uniapp商城全栈实战:从架构设计到多端上架避坑指南

简介&#xff1a;基于SpringBoot与uniapp构建的商城项目资源包&#xff0c;面向有一定Java基础和Vue.js认知、希望系统学习前后端分离开发的开发者。项目参考linjiashop开源商城设计&#xff0c;后端以SpringBoot为骨架&#xff0c;涵盖业务逻辑处理、RESTful API接口定义、JPA…

作者头像 李华
网站建设 2026/9/4 12:57:18

CPU电压安全指南:从电迁移原理到1.36V实战评估

1. 这篇文章真正要解决的问题“CPU电压1.36伏&#xff0c;长期用会不会把CPU用坏&#xff1f;”——这可能是DIY玩家和超频爱好者心中最经典、也最纠结的问题之一。你或许在B站装机猿的视频里&#xff0c;或是各大硬件论坛的帖子里&#xff0c;无数次看到类似的提问。一个看似简…

作者头像 李华
网站建设 2026/9/3 7:09:37

ATP-EMTP电磁暂态仿真全解析:原理、实操与工程避坑指南

简介&#xff1a;这是一份ATP-EMTP电力系统电磁暂态仿真工具资源包&#xff0c;面向电气工程研究人员、电力工程师及高校相关专业学生&#xff0c;可用于瞬时过电压分析、开关操作、故障仿真与保护装置校核&#xff0c;也是熟悉暂态仿真流程、复现典型故障案例的实用素材。压缩…

作者头像 李华
网站建设 2026/9/5 12:21:23

ArcGIS空间分析实战:从练习数据到完整流程的避坑指南

简介&#xff1a;ArcGIS空间分析实例与练习数据3-8章是一份面向GIS学生与从业者的配套练习资源&#xff0c;从第3章到第8章逐步覆盖缓冲区、叠置、网络、地形及栅格分析等进阶主题&#xff0c;适合已了解ArcGIS基础、想通过实操掌握空间分析流程的读者。压缩包共657个文件&…

作者头像 李华
网站建设 2026/9/4 9:08:38

Windows 11 工具链部署与 SSH 踩坑笔记

设备&#xff1a;MAXSUN MS-H110ZL / 8GB RAM / NVIDIA Quadro P1000 系统&#xff1a;Windows 11 IoT Enterprise LTSC 2024&#xff08;NTLite 精简镜像&#xff09;一、已部署工具链全部放在 C:\Users\root\AppData\Local\Microsoft\WindowsApps\&#xff08;默认在 PATH&am…

作者头像 李华
网站建设 2026/9/4 1:39:23

爱普生7710/7720免芯片固件刷机指南:从检查到恢复完整教程

简介&#xff1a;面向爱普生7710/7720打印机的固件刷机免芯片资源&#xff0c;专门解决打印机无法识别墨盒、被原厂墨盒芯片计数限制的问题&#xff0c;适用于使用填充墨盒或兼容耗材、具备一定动手能力的用户。资源包共1028个文件&#xff0c;以html帮助文档、txt说明数据和da…

作者头像 李华