JAVA面试题 从源码角度分析StringBuffer和StringBuilder的区别
作者:Java蚂蚁 发布时间:2023-07-24 07:29:07
面试官:请问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


猜你喜欢
- 基于Java的简单的用户管理系统,供大家参考,具体内容如下此系统功能和方法都比较简单本次系统通过控制台输入商品的基本信息,加入管理员的登录与
- 简介工厂方法模式是什么?为什么要有工厂方法模式,不是有了简单工厂模式了吗?两个模式都有工厂,那有什么不同呢?功工厂方式模式是怎样实现的?OK
- 自动编辑文本框(AutoCompleteTextView)继承自EditText,能够接受用户的输入编辑,但是有这自己的特色功能:输入一定的
- 一、什么是ASMASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,
- 画廊视图(Gallery)表示,能够按水平方向显示内容,并且可用手指直接拖动图片移动,一般用来浏览图片,被选中的选项位于中间,并且可以响应事
- 先看看电影票在线选座功能实现的效果图:界面比较粗糙,主要看原理。这个界面主要包括以下几部分1、座位 2、左边的排数 3、左上方的缩略图 4、
- 目的官方的Drools范例大都是基于纯Java项目或Maven项目,而基于Spring Boot项目的很少。本文介绍如何在Spring Bo
- 原理 Redis Cluster 一般由多个节点组成,节点数量至少为 6 个才能保证组成完整高可用的集群,其中三个为主
- 本文主要解决以下几个问题:我们都知道Android的刷新频率是60帧/秒,这是不是意味着每隔16ms就会调用一次onDraw方法?如果界面不
- 目录之前的处理方式现在的处理方式其他场景处理重复点击间接设置点击富文本列表数据绑定总结项目地址一般手机上的 Android App,主要的交
- 本文实例讲述了Java算法之最长公共子序列问题(LCS)。分享给大家供大家参考,具体如下:问题描述:一个给定序列的子序列是在该序列中删去若干
- 我们知道,多线程是Android开发中必现的场景,很多原生API和开源项目都有多线程的内容,这里简单总结和探讨一下常见的多线程切换方式。我们
- Android 图片切换器这几天一直在整理组件想留着以后使用.还是一点一点整理吧.今天把上周整理的 ImageSwitcher 和单位转换器
- 本文实例讲述了java Swing组件setBounds()简单用法。分享给大家供大家参考,具体如下:先看API:public void s
- 本文为大家分享了java实现水果超市管理系统的具体代码,供大家参考,具体内容如下首先建立水果类的界面public class Fruit {
- 数学原理:  
- 布局布局效果如下,下面每个“网格”都是一个按钮,点击按钮,就会有相应的事件发生。由于UniformG
- 一、前言对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外。二、MyBatis的初始化做了什么2.1 Mybatis的
- 引言Android studio 是2020 年的版本,有点老,昨天突发想法,升级到了 Android Studio Electric Ee
- 一:item的根布局设置Android:clickable="true",之后导致item点击事件失效,对根