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的,表示不可被继承 ...
随机推荐
- 系统的讲解 - SSO单点登录
目录 概念 好处 技术实现 小结 扩展 概念 SSO 英文全称 Single Sign On,单点登录. 在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统. 比如:淘宝网(www.t ...
- android使用.9图作为背景,内容不能居中的问题解决方案
在xml中使用.9图作为背景,内容不能居中,试了好多方法最后,加一个属性就ok了. android:padding:0dip; 解析:.9图作为背景时,不可拉伸的部分就相当于该空间的padding距离 ...
- .Net 反射学习
Why?为什么使用反射 MVC ORM EF 都是用的反射.反射可以让程序的扩展性,灵活性得到加强.一起即可动态创建 what 反射原理 动态加载类库 ,先添加引用类库,或者复制debug里 ...
- 手把手教你整合SSM框架(基于课工厂+MyEclipse 2017 CI 10)
步骤1:myeclipse创建项目,导入spring框架 整合思路:因为spring和spring mvc同源,可以无缝整合,故先整合spring+mybatis,然后配置web.xml.spring ...
- windows设置照片查看器为默认的照片查看软件
复制一下内容到记事本中: 文件名:PotoView.bat 文件内容: @echo off set reg_dir=hklm\SOFTWARE\Microsoft\Windows Photo View ...
- mysql7笔记----遍历节点所有子节点
mysql遍历节点的所有子节点 DELIMITER // CREATE FUNCTION `getChildrenList`(rootId INT) ) BEGIN ); ); SET sTemp = ...
- mssql sqlserver 不固定行转列数据(动态列)
转自:http://www.maomao365.com/?p=5471 摘要: 下文主要讲述动态行列转换语句,列名会根据行数据的不同, 动态的发生变化 ------------------------ ...
- linux open write lseek的API和应用
1, open #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(c ...
- scrapy爬虫 快速入门
Scrapy 1. 简介 Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架. 其可以应用在数据挖掘,信息处理或存储历史数据等一系列的程序中.其最初是为了页面抓取 (更确切来说, 网络 ...
- Java遍历List集合的4种方式
public class Test { public static void main(String[] args) { // 循环遍历List的4中方法 List<String> str ...