面试官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效率高,但是线程是不安全的。

各位老铁如果还有别的答案,可以评论留言哈!

 

JAVA面试题 StringBuffer和StringBuilder的区别,从源码角度分析?的更多相关文章

  1. Java String,StringBuffer和StringBuilder的区别

    [可变与不可变] String是字符串常量,不可变. StringBuffer和StringBuilder是字符串变量,可变. [执行速度方面] StringBuilder > StringBu ...

  2. java中String,StringBuffer与StringBuilder的区别??

    本文着重介绍下,应该在何时恰当的使用string,stringbuffer,stringbuilder. 1,执行速度 StringBuilder >  StringBuffer  >  ...

  3. [Java]String、 StringBuffer、StringBuilder的区别

    一.异同点: 1) 都是 final 类, 都不允许被继承; 2) String 长度是不可变的, StringBuffer.StringBuilder 长度是可变的; 3) StringBuffer ...

  4. 【转】java comparator 升序、降序、倒序从源码角度理解

    原文链接:https://blog.csdn.net/u013066244/article/details/78997869 环境jdk:1.7+ 前言之前我写过关于comparator的理解,但是都 ...

  5. 【Java】String,StringBuffer与StringBuilder的区别??

    String 字符串常量StringBuffer 字符串变量(线程安全)StringBuilder 字符串变量(非线程安全) 简要的说, String 类型和 StringBuffer 类型的主要性能 ...

  6. Java基础之StringBuffer和StringBuilder的区别

    StringBuffer是一个字符串的缓存类,属于一个容器,对于容器,我们可以进行增删改查. StringBuffer的容器长度是可变的,并且里面可以存放多种的数据类型.它跟其他容器,比如数组,是很不 ...

  7. Java面试题 从源码角度分析HashSet实现原理?

    面试官:请问HashSet有哪些特点? 应聘者:HashSet实现自set接口,set集合中元素无序且不能重复: 面试官:那么HashSet 如何保证元素不重复? 应聘者:因为HashSet底层是基于 ...

  8. Java问题解读系列之String相关---String、StringBuffer、StringBuilder的区别

    今天的题目是String.StringBuffer和StringBuilder的区别: 首先还是去官方的API看看对这三种类型的介绍吧,Go...... 一.继承类和实现接口情况 1.String类 ...

  9. JAVA String、StringBuffer、StringBuilder类解读

    JAVA String.StringBuffer.StringBuilder类解读 字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作 ...

随机推荐

  1. UWP-动态磁贴

    原文:UWP-动态磁贴 来自:IT追梦园 (http://www.zmy123.cn/?p=1172) UWP应用的一大特色就是动态磁贴,所以,你的应用如果还没有设置动态磁贴,那么,和我一起来为应用加 ...

  2. Windows应用程序文件说明

    bin文件夹:包含debug子目录,含有.exe可执行文件和pdb文件,其中pdb文件包含完整的调试信息(包含函数原型): obj文件夹:包含debug子目录,含有编译过程中生成的中间代码. Prop ...

  3. 一定要在commit之前做RAR备份,这样在出问题的时候,可以排除别人代码的干扰

    否则找错实在是太痛苦了,根本不知道来自哪里...而这样上面那样做,可以节省时间.

  4. C# ACCESS 修改表记录提示"UPDATE 语句语法错"问题

    错误的sql 语句如下: sqlStr =  "update tb_userInfo set passWord='" + pw + "' where userName=' ...

  5. Qt Resource系统概说(资源压缩不压缩都可以)

    什么是Qt Resource系统?简单的说,就是在可执行程序中存储binary文件,而且还是与平台无关的. 与Qt Resource系统密切相关的有三个法宝,分别是qmake.rcc.QFile. q ...

  6. Qt按ESC关闭模态对话框不触发closeEvent()问题解析(ESC默认调用的是reject()函数,所以必须覆盖这个函数才会有效果)good

    事情是这样的:今天调试窗体,突然发现按ESC键居然跳过closeEvent()关闭了对话框!我的关闭判断都在closeEvent()里,这直接导致非正常关闭了正在进行的工作.先重建下场景: 调用处: ...

  7. Impala概念与架构

    Impala概念与架构 下面的内容介绍Cloudera Impala的背景资料及特性,以便你更高效的使用它.Where appropriate, the explanations include co ...

  8. 分布式数据库中间件 MyCat 搞起来!

    关于 MyCat 的铺垫文章已经写了三篇了: MySQL 只能做小项目?松哥要说几句公道话! 北冥有 Data,其名为鲲,鲲之大,一个 MySQL 放不下! What?Tomcat 竟然也算中间件? ...

  9. [Java] 父类和子类拥有同名的成员变量(fields)的情况

    首先,需要明确的是,无论是通过casting,还是通过将子类对象的reference赋值给父类变量,都无法改变该reference所指对象的真实类型.但当该reference的类型是父类时,将无法调用 ...

  10. MySQL8.0 DDL原子性特性

    1. DDL原子性概述 8.0之前并没有统一的数据字典dd,server层和引擎层各有一套元数据,sever层的元数据包括(.frm,.opt,.par,.trg等),用于存储表定义,分区表定义,触发 ...