面试官 Q1: 请问 StringBuffer 和 StringBuilder 有什么区别?
这是一个老生常谈的话题, 笔者前几年每次面试都会被问到, 作为基础面试题, 被问到的概率百分之八九十. 下面我们从面试需要答到的几个知识点来总结一下两者的区别有哪些?
继承关系?
如何实现的扩容?
线程安全性?
继承关系
从源码上看看类 StringBuffer 和 StringBuilder 的继承结构:
从结构图上可以直到, StringBuffer 和 StringBuiler 都继承自 AbstractStringBuilder 类
如何实现扩容
StringBuffer 和 StringBuiler 的扩容的机制在抽象类 AbstractStringBuilder 中实现, 当发现长度不够的时候(默认长度是 16), 会自动进行扩容工作, 扩展为原数组长度的 2 倍加 2, 创建一个新的数组, 并将数组的数据复制到新数组.
- public void ensureCapacity(int minimumCapacity) {
- if (minimumCapacity> 0)
- ensureCapacityInternal(minimumCapacity);
- }
- /**
- * 确保 value 字符数组不会越界. 重新 new 一个数组, 引用指向 value
- */
- private void ensureCapacityInternal(int minimumCapacity) {
- // overflow-conscious code
- if (minimumCapacity - value.length> 0) {
- value = Arrays.copyOf(value,
- newCapacity(minimumCapacity));
- }
- }
- /**
- * 扩容: 将长度扩展到之前大小的 2 倍 + 2
- */
- private int newCapacity(int minCapacity) {
- // overflow-conscious code 扩大 2 倍 + 2
- // 这里可能会溢出, 溢出后是负数哈, 注意
- int newCapacity = (value.length <<1) + 2;
- if (newCapacity - minCapacity < 0) {
- newCapacity = minCapacity;
- }
- //MAX_ARRAY_SIZE 的值是 Integer.MAX_VALUE - 8, 先判断一下预期容量 (newCapacity) 是否在 0<x<MAX_ARRAY_SIZE 之间, 在这区间内就直接将数值返回, 不在这区间就去判断一下是否溢出
- return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
- ? hugeCapacity(minCapacity)
- : newCapacity;
- }
- /**
- * 判断大小, 是否溢出
- */
- private int hugeCapacity(int minCapacity) {
- if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
- throw new OutOfMemoryError();
- }
- return (minCapacity> MAX_ARRAY_SIZE)
- ? minCapacity : MAX_ARRAY_SIZE;
- }
线程安全性
我们先来看看 StringBuffer 的相关方法:
- @Override
- public synchronized StringBuffer append(long lng) {
- toStringCache = null;
- super.append(lng);
- return this;
- }
- /**
- * @throws StringIndexOutOfBoundsException {@inheritDoc}
- * @since 1.2
- */
- @Override
- public synchronized StringBuffer replace(int start, int end, String str) {
- toStringCache = null;
- super.replace(start, end, str);
- return this;
- }
- /**
- * @throws StringIndexOutOfBoundsException {@inheritDoc}
- * @since 1.2
- */
- @Override
- public synchronized String substring(int start) {
- return substring(start, count);
- }
- @Override
- public synchronized String toString() {
- if (toStringCache == null) {
- toStringCache = Arrays.copyOfRange(value, 0, count);
- }
- return new String(toStringCache, true);
- }
从上面的源码中我们看到几乎都是所有方法都加了 synchronized, 几乎都是调用的父类的方法., 用 synchronized 关键字修饰意味着什么? 加锁, 资源同步串行化处理, 所以是线程安全的.
我们再来看看 StringBuilder 的相关源码:
- @Override
- public StringBuilder append(double d) {
- super.append(d);
- return this;
- }
- /**
- * @since 1.5
- */
- @Override
- public StringBuilder appendCodePoint(int codePoint) {
- super.appendCodePoint(codePoint);
- return this;
- }
- /**
- * @throws StringIndexOutOfBoundsException {@inheritDoc}
- */
- @Override
- public StringBuilder delete(int start, int end) {
- super.delete(start, end);
- return this;
- }
StringBuilder 的源码里面, 基本上所有方法都没有用 synchronized 关键字修饰, 当多线程访问时, 就会出现线程安全性问题.
为了证明 StringBuffer 线程安全, StringBuilder 线程不安全, 我们通过一段代码进行验证:
测试思想
分别用 1000 个线程写 StringBuffer 和 StringBuilder,
使用 CountDownLatch 保证在各自 1000 个线程执行完之后才打印 StringBuffer 和 StringBuilder 长度,
观察结果.
测试代码
- import java.util.concurrent.CountDownLatch;
- public class TestStringBuilderAndStringBuffer {
- public static void main(String[] args) {
- // 证明 StringBuffer 线程安全, StringBuilder 线程不安全
- StringBuffer stringBuffer = new StringBuffer();
- StringBuilder stringBuilder = new StringBuilder();
- CountDownLatch latch1 = new CountDownLatch(1000);
- CountDownLatch latch2 = new CountDownLatch(1000);
- for (int i = 0; i < 1000; i++) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- stringBuilder.append(1);
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- latch1.countDown();
- }
- }
- }).start();
- }
- for (int i = 0; i < 1000; i++) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- stringBuffer.append(1);
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- latch2.countDown();
- }
- }
- }).start();
- }
- try {
- latch1.await();
- System.out.println(stringBuilder.length());
- latch2.await();
- System.out.println(stringBuffer.length());
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
测试结果
StringBuffer 不论运行多少次都是 1000 长度.
StringBuilder 绝大多数情况长度都会小于 1000.
StringBuffer 线程安全, StringBuilder 线程不安全得到证明.
总结一下
StringBuffer 和 StringBuilder 都继承自抽象类 AbstractStringBuilder.
存储数据的字符数组也没有被 final 修饰, 说明值可以改变, 且构造出来的字符串还有空余位置拼接字符串, 但是拼接下去肯定也有不够用的时候, 这时候它们内部都提供了一个自动扩容机制, 当发现长度不够的时候(默认长度是 16), 会自动进行扩容工作, 扩展为原数组长度的 2 倍加 2, 创建一个新的数组, 并将数组的数据复制到新数组, 所以对于拼接字符串效率要比 String 要高. 自动扩容机制是在抽象类中实现的.
线程安全性: StringBuffer 效率低, 线程安全, 因为 StringBuffer 中很多方法都被 synchronized 修饰了, 多线程访问时, 线程安全, 但是效率低下, 因为它有加锁和释放锁的过程. StringBuilder 效率高, 但是线程是不安全的.
各位老铁如果还有别的答案, 可以评论留言哈!
来源: https://www.cnblogs.com/marsitman/p/11204313.html