String-StringBuffer-StringBuilder的区别和源码分析
一,String,StringBuffer,StringBuilder三者之间的关系
三个类的关系:StringBuffer和StringBuilder都继承自AbstractStringBuilder这个类,
而AbstractStringBuilder和String都继承自Object这个类(Object是所有java类的超类)
可以通过如下的部分源码看到:
String:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
。。。。。
}
StringBuffer:
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{
。。。
}
StringBuilder:
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{
。。。。。
}
二,String是不可变类,而StringBuffer, StringBuilder是可变类
我们查看这三个类的源码,发现String类没有append()、delete()、insert()这三个成员方法,而StringBuffer和StringBuilder都有这些方法,StringBuffer和StringBuilder中的这三个方法都是通过system类的arraycopy方法来实现的,即将原数组复制到目标数组
1.String类对字符串的截取操作
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
//当对原来的字符串进行截取的时候(beginIndex >0),返回的结果是新建的对象
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
当我们对字符串从第beginIndex(beginIndex >0) 个字符开始进行截取时,返回的结果是重新new出来的对象。所以,在对String类型的字符串进行大量“插入”和“删除”操作时会产生大量的临时变量。
2.StringBuffer与StringBuilder:
因为StringBuffer和StringBuilder都继承自这个抽象类,即AbstractStringBuilder类是StringBuffer和StringBuilder的共同父类。AbstractStringBuilder类的关键代码片段如下:
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;//一个char类型的数组,非final类型,这一点与String类不同
/**
* This no-arg constructor is necessary for serialization of subclasses.
*/
AbstractStringBuilder() {
}
/**
* Creates an AbstractStringBuilder of the specified capacity.
*/
AbstractStringBuilder(int capacity) {
value = new char[capacity];//构建了长度为capacity大小的数组
}
//其他代码省略……
……
}
StringBuffer:
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
/**
* Constructs a string buffer with no characters in it and an
* initial capacity of 16 characters.
*/
public StringBuffer() {
super(16);//创建一个默认大小为16的char型数组
} /**
* Constructs a string buffer with no characters in it and
* the specified initial capacity.
*
* @param capacity the initial capacity.
* @exception NegativeArraySizeException if the {@code capacity}
* argument is less than {@code 0}.
*/
public StringBuffer(int capacity) {
super(capacity);//自定义创建大小为capacity的char型数组
}
//省略其他代码……
StringBuilder类的构造函数与StringBuffer类的构造函数实现方式相同,此处就不贴代码了。
下面来看看StringBuilder类的append方法和insert方法的代码,因StringBuilder和StringBuffer的方法实现基本上一致,不同的是StringBuffer类的方法前多了个synchronized关键字,
即StringBuffer是线程安全的。所以接下来我们就只分析StringBuilder类的代码了。StringBuilder类的append方法,insert方法都是Override 父类AbstractStringBuilder的方法,
所以我们直接来分析AbstractStringBuilder类的相关方法。
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
//调用下面的ensureCapacityInternal方法
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
//调用下面的expandCapacity方法实现“扩容”特性
expandCapacity(minimumCapacity);
}
/**
* This implements the expansion semantics of ensureCapacity with no
* size check or synchronization.
*/
void expandCapacity(int minimumCapacity) {
//“扩展”的数组长度是按“扩展”前数组长度的2倍再加上2 byte的规则来扩展
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
//将value变量指向Arrays返回的新的char[]对象,从而达到“扩容”的特性
value = Arrays.copyOf(value, newCapacity);
}
从上述代码分析得出,StringBuilder和StringBuffer的append方法“扩容”特性本质上是通过调用Arrays类的copyOf方法来实现的。接下来我们顺藤摸瓜,再分析下Arrays.copyOf(value, newCapacity)这个方法吧。代码如下:
public static char[] copyOf(char[] original, int newLength) {
//创建长度为newLength的char数组,也就是“扩容”后的char 数组,并作为返回值
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;//返回“扩容”后的数组变量
}
其中,insert方法也是调用了expandCapacity方法来实现“扩容”特性的,此处就不在赘述了。
接下来,分析下delete(int start, int end)方法,代码如下:
public AbstractStringBuilder delete(int start, int end) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
end = count;
if (start > end)
throw new StringIndexOutOfBoundsException();
int len = end - start;
if (len > 0) {
//调用native方法arraycopy对value数组进行复制操作,然后重新赋值count变量达到“删除”特性
System.arraycopy(value, start+len, value, start, count-end);
count -= len;
}
return this;
}
从源码可以看出delete方法的“删除”特性是调用native方法arraycopy对value数组进行复制操作,然后重新赋值count变量实现的
最后,来看下substring方法,源码如下 :
public String substring(int start, int end) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
throw new StringIndexOutOfBoundsException(end);
if (start > end)
throw new StringIndexOutOfBoundsException(end - start);
//根据start,end参数创建String对象并返回
return new String(value, start, end - start);
}
三,运行速度:
总体上:String小于StringBuffer小于StringBuilder
原因:String是不可变的,为字符串常量,每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象。这就会对程序运行产生很大的影响,因为当内存中的无引用对象多了以后,JVM的GC进程就会进行垃圾回收,这个过程会耗费很长一段时间,因此经常改变内容的字符串最好不要用 String类的对象。而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。
注意:但是在某些特殊情况下,String对象的字符串拼接其实是被JVM解释成了StringBuffer对象的拼接,所以这时候String对象的速度并不比StringBuffer对象慢。如:
String m = "I"+"am"+"boy";
StringBuffer n = new StringBuffer("I").append("am").append("boy");
但是如果要拼接的字符串来自于不同的String对象的话,那结果就不一样了(常用),StringBuffer要比String快的多,如:
String p = "China is";
String q = "very good";
String t = p + q;
在运行速度方面StringBuffer<StringBuilder,这是因为StringBuffer由于线程安全的特性,常常应用于多线程的程序中,为了保证多线程同步一些线程就会遇到阻塞的情况,这就使得StringBuffer的运行时间增加,从而使得运行速度减慢;而StringBuilder通常不会出现多线程的情况,所以运行时就不会被阻塞,运行速度也自然就比StringBuffer快了。
StringBuffer和StringBuilder性能测试程序:
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
long builderThreadStartTime = (new Date()).getTime();//builder 多线程开始时间
for(int i=0;i<10000000;i++){
sb.append(i);
}
long builderThreadEndTime = (new Date()).getTime();//builder 多线程结束时间
System.out.println("builder 多线程消耗时间==="+(builderThreadEndTime-builderThreadStartTime));
StringBuffer sbf = new StringBuffer();
long bufferThreadStartTime = (new Date()).getTime();//buffer 单线程开始时间
for(int i=0;i<10000000;i++){
sbf.append(i);
}
long bufferThreadEndTime = (new Date()).getTime();//buffer 单线程结束时间
System.out.println("buffer 单线程消耗时间==="+(bufferThreadEndTime-bufferThreadStartTime));
}
输出结果:
builder 多线程消耗时间===391
buffer 单线程消耗时间===611
四,线程安全与不安全
StringBuffer是线程安全的,StringBuilder是非线程安全的
部分源码:StringBuffer:
public synchronized String substring(int start) {
return substring(start, count);
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @since 1.4
*/
@Override
public synchronized CharSequence subSequence(int start, int end) {
return super.substring(start, end);
}
/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
* @since 1.2
*/
@Override
public synchronized String substring(int start, int end) {
return super.substring(start, end);
}
/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
* @since 1.2
*/
@Override
public synchronized StringBuffer insert(int index, char[] str, int offset,
int len)
{
toStringCache = null;
super.insert(index, str, offset, len);
return this;
}
/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
*/
@Override
public synchronized StringBuffer insert(int offset, Object obj) {
toStringCache = null;
super.insert(offset, String.valueOf(obj));
return this;
}
StringBuilder:
@Override
public StringBuilder append(long lng) {
super.append(lng);
return this;
} @Override
public StringBuilder append(float f) {
super.append(f);
return this;
} @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;
}
我们可以发现StringBuffer类中的大部分成员方法都被synchronized关键字修饰,而StringBuilder类没有出现synchronized关键字;至于StringBuffer类中那些没有用synchronized修饰的成员方法,如insert()、indexOf()等,通过源码上的注释可以知道,它们是调用StringBuffer类的其他方法来实现同步的。注意:toString()方法也是被synchronized关键字修饰的
五,synchronized关键字解析:
一>、修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 当两个并发线程访问同一个对象中的synchronized(this){}同步代码块时,同一时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块;
- 当一个线程访问对象中的一个synchronized(this){}同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this){}同步代码块;
- 当一个线程访问对象中的一个synchronized(this){}同步代码块时,其他线程对对象中所有其它synchronized(this){}同步代码块的访问将被阻塞。
二>、修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
三>、修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 因为静态方法(或变量)是属于其所属类的,而不是属于该类的对象的,所以synchronized关键字修饰的静态方法锁定的是这个类的所有对象,即所有对象都是同一把锁。
六, String, StringBuffer, StringBuilder都能够用final关键字修饰,此处不再过多解释。
但需要注意的是:
- final修饰的类不能被继承;
- final修饰的方法不能被继承类重写;
- final修饰的变量为常量,不能被改变。
七,总结:
1、String类型的字符串对象是不可变的,一旦String对象创建后,包含在这个对象中的字符系列是不可以改变的,直到这个对象被销毁。
2、StringBuilder和StringBuffer类型的字符串是可变的,不同的是StringBuffer类型的是线程安全的,而StringBuilder不是线程安全的
3、如果是多线程环境下涉及到共享变量的插入和删除操作,StringBuffer则是首选。如果是非多线程操作并且有大量的字符串拼接,插入,删除操作则StringBuilder是首选。毕竟String类是通过创建临时变量来实现字符串拼接的,耗内存还效率不高,怎么说StringBuilder是通过JNI方式实现终极操作的。
4、StringBuilder和StringBuffer的“可变”特性总结如下:
(1)append,insert,delete方法最根本上都是调用System.arraycopy()这个方法来达到目的
(2)substring(int, int)方法是通过重新new String(value, start, end - start)的方式来达到目的。因此,在执行substring操作时,StringBuilder和String基本上没什么区别。
5、为什么String是Final类型,确可以进行+等操作呢?
答:因为String的+操作实际是通过StringBuffer的append方法进行操作,然后又通过StringBuffer的toString()操作重新赋值的。
本文参考博客:https://www.cnblogs.com/Wilange/p/7570633.html
https://blog.csdn.net/hj7jay/article/details/52770174
String-StringBuffer-StringBuilder的区别和源码分析的更多相关文章
- String,StringBuffer,StringBuilder的区别及其源码分析
String,StringBuffer,StringBuilder的区别这个问题几乎是面试必问的题,这里做了一些总结: 1.先来分析一下这三个类之间的关系 乍一看它们都是用于处理字符串的java类,而 ...
- [置顶] String StringBuffer StringBuilder的区别剖析
这是一道很常见的面试题目,至少我遇到过String/StringBuffer/StringBuilder的区别:String是不可变的对象(final)类型,每一次对String对象的更改均是生成一个 ...
- String,StringBuffer,StringBuilder的区别
public static void main(String[] args) { String str = new String("hello...."); StringBuffe ...
- Question 20171115 String&&StringBuffer&&StringBuilder的区别与联系?
Question 20171114 String&&StringBuffer&&StringBuilder的区别和联系 创建成功的String对象,其长度是固定的,内容 ...
- java中 String StringBuffer StringBuilder的区别
* String类是不可变类,只要对String进行修改,都会导致新的对象生成. * StringBuffer和StringBuilder都是可变类,任何对字符串的改变都不会产生新的对象. 在实际使用 ...
- 深入理解String, StringBuffer, StringBuilder的区别(基于JDK1.8)
String.StringBuffer.StringBuilder都是JAVA中常用的字符串操作类,对于他们的区别大家也都能耳熟能详,但底层到底是怎样实现的呢?今天就再深入分析下这三种字符串操作的区别 ...
- Android/Java 中的 String, StringBuffer, StringBuilder的区别和使用
Android 中的 String, StringBuffer 和 StringBuilder 是移动手机开发中经常使用到的字符串类.做为基础知识是必须要理解的,这里做一些总结. A.区别 可以从以下 ...
- 在JAVA中,String,Stringbuffer,StringBuilder 的区别
首先是,String,StringBuffer的区别 两者的主要却别有两方面,第一是线程安全方面,第二是效率方面 线程安全方面: String 不是线程安全的,这意味着在不同线程共享一个String ...
- 从源码看String,StringBuffer,StringBuilder的区别
前言 看了一篇文章,大概是讲面试中的java基础的,有如题这么个面试题.我又翻了一些文章看了下,然后去看源码.看一下源码大概能更加了解一些. String String类是final的,表示不可被继承 ...
随机推荐
- Spark学习之Spark调优与调试(一)
一.使用SparkConf配置Spark 对 Spark 进行性能调优,通常就是修改 Spark 应用的运行时配置选项.Spark 中最主要的配置机制是通过 SparkConf 类对 Spark 进行 ...
- PHP全栈学习笔记12
php简介,php历史,php后端工程师职业前景,php技术方向,php后端工程师职业体系介绍. php是世界上使用最广泛的web开发语言,是超文本预处理器,是一种通用的开源脚本语言,语法吸收了c语言 ...
- .NET Core IdentityServer4实战 第三章-使用EntityFramework Core进行持久化配置
内容:本文带大家使用IdentityServer4进行使用使用EntityFramework Core进行配置和操作数据 作者:zara(张子浩) 欢迎分享,但需在文章鲜明处留下原文地址. 前两章内容 ...
- 基于ITextSharp插件在ASP.NET MVC中将图表导出为PDF
样本: 在这个示例中,我们使用的是微软给我们提供的数据库,也就是家喻户晓的Northwind数据库.要下载Microsoft的免费样本Northwind数据库,您需要访问以下URL.下载Northwi ...
- Mysql常用基础操作(备忘录)
常常忘记mysql的一些命令行操作,甚至于说,比较复杂的sql格式记不住或忘记了,也可能根本不会考虑去记,因此,做一下汇总,当下次出现恍惚时不至于去百度挨个找,有时就是记不起来,但是只要给点药引子,立 ...
- Element-ui使用技巧
使用第三方字体包 把下载后的*.zip字体包放到项目中在main.js中引用. import "@/assets/font/iconfont.css"; 注意一定要放到elemen ...
- Mqtt学习指南
MQTT是物联网应用当中一种非常重要的,轻量级的协议,现将该协议的重要学习资源整理一下,希望能为初学者提供一个完整的学习资源. MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议.它的设计思 ...
- 持续集成之 Nuget 进阶
持续集成之 Nuget 进阶 Intro 之前介绍了一篇基于 Azure pipeline 的 nuget 包的持续集成配置,但是比较粗糙,这里介绍一下结合 Cake 实现更优雅的 nuget 包发布 ...
- LOJ #6052. 「雅礼集训 2017 Day11」DIV
完了我是数学姿势越来越弱了,感觉这种CXRdalao秒掉的题我都要做好久 一些前置推导 首先我们很容易得出\((a+bi)(c+di)=k \Leftrightarrow ac-bd=k,ad+bc= ...
- 12.22 大湾区.NET Meet 大会
今年的 Connect(); 主题更加聚焦开发者工具生产力.开源,以及无服务器(Serverless)云服务. Visual Studio 2019 AI 智能加持的 IntelliCode.实时代码 ...