一,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的区别和源码分析的更多相关文章

  1. String,StringBuffer,StringBuilder的区别及其源码分析

    String,StringBuffer,StringBuilder的区别这个问题几乎是面试必问的题,这里做了一些总结: 1.先来分析一下这三个类之间的关系 乍一看它们都是用于处理字符串的java类,而 ...

  2. [置顶] String StringBuffer StringBuilder的区别剖析

    这是一道很常见的面试题目,至少我遇到过String/StringBuffer/StringBuilder的区别:String是不可变的对象(final)类型,每一次对String对象的更改均是生成一个 ...

  3. String,StringBuffer,StringBuilder的区别

    public static void main(String[] args) { String str = new String("hello...."); StringBuffe ...

  4. Question 20171115 String&&StringBuffer&&StringBuilder的区别与联系?

    Question 20171114 String&&StringBuffer&&StringBuilder的区别和联系 创建成功的String对象,其长度是固定的,内容 ...

  5. java中 String StringBuffer StringBuilder的区别

    * String类是不可变类,只要对String进行修改,都会导致新的对象生成. * StringBuffer和StringBuilder都是可变类,任何对字符串的改变都不会产生新的对象. 在实际使用 ...

  6. 深入理解String, StringBuffer, StringBuilder的区别(基于JDK1.8)

    String.StringBuffer.StringBuilder都是JAVA中常用的字符串操作类,对于他们的区别大家也都能耳熟能详,但底层到底是怎样实现的呢?今天就再深入分析下这三种字符串操作的区别 ...

  7. Android/Java 中的 String, StringBuffer, StringBuilder的区别和使用

    Android 中的 String, StringBuffer 和 StringBuilder 是移动手机开发中经常使用到的字符串类.做为基础知识是必须要理解的,这里做一些总结. A.区别 可以从以下 ...

  8. 在JAVA中,String,Stringbuffer,StringBuilder 的区别

    首先是,String,StringBuffer的区别 两者的主要却别有两方面,第一是线程安全方面,第二是效率方面 线程安全方面: String  不是线程安全的,这意味着在不同线程共享一个String ...

  9. 从源码看String,StringBuffer,StringBuilder的区别

    前言 看了一篇文章,大概是讲面试中的java基础的,有如题这么个面试题.我又翻了一些文章看了下,然后去看源码.看一下源码大概能更加了解一些. String String类是final的,表示不可被继承 ...

随机推荐

  1. Spring里的Async注解实现异步操作

    异步执行一般用来发送一些消息数据,数据一致性不要求太高的场景,对于spring来说,它把这个异步进行了封装,使用一个注解就可以实现. 用法 程序启动时开启@EnableAsync注解 建立新的类型,建 ...

  2. 从壹开始前后端分离 39 || 想创建自己的dotnet模板么?看这里

    缘起 开工是利啦!哈喽各位小伙伴,周三好呀,新的一年又开始了,老张给大家做个榜样,新的一年也要好好学习哟,这两天闲的无事就整理整理了这个系列的 Github 代码,增加了一个英文的 README ,然 ...

  3. 流程控制之if判断

    目录 语法(掌握) if if...else if...elif...else 练习(掌握) 练习1:成绩评判 练习2:模拟登录注册 if的嵌套(掌握) 语法(掌握) if判断是干什么的呢?if判断其 ...

  4. MyBatis中主键回填的两种实现方式

    主键回填其实是一个非常常见的需求,特别是在数据添加的过程中,我们经常需要添加完数据之后,需要获取刚刚添加的数据 id,无论是 Jdbc 还是各种各样的数据库框架都对此提供了相关的支持,本文我就来和和大 ...

  5. Python:logging 的巧妙设计

    引言 logging 的基本用法网上很多,这里就不介绍了.在引入正文之前,先来看一个需求: 假设需要将某功能封装成类库供他人使用,如何处理类库中的日志? 数年前在一个 C# 开发的项目中,我用了这样的 ...

  6. flex 布局实现固定头部和底部,中间滚动布局

    关键词:display: flex,flex: 1,  overflow-y: scroll; 实现:head 和footer 固定,中间body多了滚动,少了撑满: head和footer宽度根据内 ...

  7. odoo:免费开源ERP入门与实践

    Odoo下载安装 Odoo社区版下载链接:http://nightly.odoo.com/ ,下载11.0社区版Windows安装包:http://nightly.odoo.com/11.0/nigh ...

  8. 【原】无脑操作:Chrome浏览器安装Vue.js devtool

    学习Vue.js时,Chrome浏览器安装Vue.js devtool能很方便的查看Vue对象.组件.事件等. 本文以Chrome浏览器插件Vue.js devtools_3.1.2_0.crx的安装 ...

  9. June 30th. 2018, Week 26th. Saturday

    Curiosity is the wick in the candle of learning. 如果学习是一根蜡烛,那好奇心就是烛芯. From William Arthur Ward. Pleas ...

  10. Exp3免杀原理与实践 20164312 马孝涛

    1.实验要求   1.1 正确使用msf编码器(0.5分),msfvenom生成如jar之类的其他文件(0.5分),veil-evasion(0.5分),加壳工具(0.5分),使用shellcode编 ...