面试官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. Arch Linux 是个 针对 i686 优化的 Linux 发行版(通过可以轻松使用的二进制包系统 - pacman)

    Arch Linux 是个 针对 i686 优化的 Linux 发行版(通过可以轻松使用的二进制包系统 - pacman)Arch 同时也拥有一个类似 ports 的包构建系统(Arch Build ...

  2. python中时间相关问题,仅作为笔记

    1.获取当前时间的两种方法: import datetime,timenow = time.strftime("%Y-%m-%d %H:%M:%S")print nownow = ...

  3. Dynamic linking is coming to iOS, tvOS, and watchOS ports of Qt in the 5.9 release

    http://blog.qt.io/blog/2017/01/23/qt-5-8-released/ Dynamic linking is coming to iOS, tvOS, and watch ...

  4. Model1简介

    Model1模型出现前,整个Web应用的情况:几乎全部由JSP页面组成,JSP页面接收处理客户端请求,对请求处理后直接做出响应. 弊端:在界面层充斥着大量的业务逻辑的代码和数据访问层的代码,Web程序 ...

  5. 一次C#和C++的实际应用性能比较(C++允许我们使用任何手段来提高效率,只要愿意做出足够的努力)

    05年时,在微软的Rico Mariani做了一次实际应用的C#和C++的性能比较.事情起源于微软著名的元老Raymond Chen(在下敬仰的超级牛人)用C++写了一个英汉词典程序,来描述讲解优化C ...

  6. Dedecms 中,获取某一栏目所有子栏目

    以前从来没写过递归(其实想想,对算法完全没概念),刚好有这个需求,试着写了一下,发现也挺容易的,特别记录一下. 数据库是dedecms默认的,dede_arctype是保存栏目的表,reid是栏目的父 ...

  7. Ubuntu 下压缩软件的安装

    在ubuntu下,系统就自带一个压缩包管理软件,但是,它默认是不支持rar和7zip格式的.因此,我们可以给它直接“增强”一下.就成了万能的了.安装方法,终端里面: sudo apt-get inst ...

  8. lock和synchronized如何选择?

    1.lock是一个接口,而synchronized是java关键字,synchronized是内置的语言实现. 2.synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁,而l ...

  9. 高并发 Nginx+Lua OpenResty系列(6)——Lua开发库Mysql

    Mysql客户端 lua-resty-mysql是为基于cosocket API的ngx_lua提供的Lua Mysql客户端,通过它可以完成Mysql的操作.默认安装OpenResty时已经自带了该 ...

  10. 原子操作CAS-最小的线程安全

    原文连接:(http://www.studyshare.cn/blog-front/blog/details/1166/0 )一.原子操作是什么? 如果有两个线程分别执行两个操作A和B,从第一个线程执 ...