博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
StringBuffer&StringBuilder详解
阅读量:6251 次
发布时间:2019-06-22

本文共 7624 字,大约阅读时间需要 25 分钟。

hot3.png

序言

StringBuffer与StringBuilder是java.lang包下被大家熟知的两个类。

其异同为:

一、长度都是可扩充的;

二、StringBuffer是线程安全的,StringBuilder是线程不安全的。

那么他们的长度是如何实现动态扩充以及StringBuffer的线程安全是如何实现的呢?通过“深度”阅读它们的源代码,最终弄明白其中的缘由。

正文

首先上一张StringBuffer和StringBuilder类结构图:

抽象类AbstractStringBuilder(也是核心实现类)实现了Appendable和CharSequence两个接口;StringBuffer与StringBuilder统统继承自AbstractStringBuilder,并且实现了java.io.Serializable和CharSequence接口。

下面简单描述下这几个接口所起到的作用(引用自中文api)。

  • Appendable: 能够被添加 char 序列和值的对象。如果某个类的实例打算接收 java.util.   的格式化输出,那么该类必须实现 Appendable 接口。要添加的字符应该是有效的 Unicode 字符,正如    中描述的那样。注意,增补字符可能由多个 16 位char 值组成。
  • CharSequence:CharSequence 是 char 值的一个可读序列。此接口对许多不同种类的 char 序列提供统一的只读访问。char 值表示 Basic Multilingual Plane (BMP) 或代理项中的一个字符。有关详细信息,请参阅 。此接口不修改  和  方法的常规协定。因此,通常未定义比较实现 CharSequence 的两个对象的结果。每个对象都可以通过一个不同的类实现,而且不能保证每个类能够测试其实例与其他类的实例的相等性。因此,使用任意 CharSequence 实例作为集合中的元素或映射中的键是不合适的。

 

  • Serializable:类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。 

 

  • AbstractStringBuilder这个抽象类提供了StringBuffer和StringBuilder绝大部分的实现。在 AbstractStringBuilder 的描述中说:如果去掉线程安全,那么 StringBuffer和StringBuilder是完全一致的。从实现的角度来说,StringBuffer所有方法(构造方法除外,因为没有必要)签名中都使用 synchronized 限定,也就是所有的方法都是同步的 。

 eg:StringBuffer中replace()方法

public synchronized StringBuffer replace(int start, int end, String str) {        super.replace(start, end, str);        return this;}

StringBuilder中replace():

public StringBuilder replace(int start, int end, String str) {        super.replace(start, end, str);        return this;}

区别仅仅在方法签名上是否有synchronized。

另外需要稍稍注意的问题是:StringBuffer同步只同步目标,比如:sb.append("i am not synchronized"),sb是同步的,而其中的参数未必是同步的。

 

而它们两个可扩展长度则是通过ensureCapacity(int minimumCapacity)来验证当前长度是否小于参数minimumCapacity,如果成立则进行分配空间。分配新空间的步长为(当前长度+1)的两倍。

实现如下:

public void ensureCapacity(int minimumCapacity) {        if (minimumCapacity > value.length) {            expandCapacity(minimumCapacity);        }}void expandCapacity(int minimumCapacity) {        int newCapacity = (value.length + 1) * 2;        if (newCapacity < 0) {            newCapacity = Integer.MAX_VALUE;        } else if (minimumCapacity > newCapacity) {            newCapacity = minimumCapacity;        }        value = Arrays.copyOf(value, newCapacity);}

如果新的长度小于0(溢出了),则使用Integer的最大值作为长度。

 

另外,在阅读源码的过程中,发现两个有趣的问题,下面一一道来。

 

第一个,就是reverse的实现。其实我是第一次看StringBuilder和StringBuffer的源码,这里面的reverse的实现是我所知道的java中的最高效的实现,没有之一。

上源码,再做解释:

public AbstractStringBuilder reverse() {        boolean hasSurrogate = false;        int n = count - 1;        for (int j = (n-1) >> 1; j >= 0; --j) {            char temp = value[j];            char temp2 = value[n - j];            if (!hasSurrogate) {                hasSurrogate = (temp >= Character.MIN_SURROGATE && temp <= Character.MAX_SURROGATE)                    || (temp2 >= Character.MIN_SURROGATE && temp2 <= Character.MAX_SURROGATE);            }            value[j] = temp2;            value[n - j] = temp;        }        if (hasSurrogate) {            // Reverse back all valid surrogate pairs            for (int i = 0; i < count - 1; i++) {                char c2 = value[i];                if (Character.isLowSurrogate(c2)) {                    char c1 = value[i + 1];                    if (Character.isHighSurrogate(c1)) {                        value[i++] = c1;                        value[i] = c2;                    }                }            }        }        return this;}

reverse分成两个部分:前面一个循环与后面的判断。

首先地一个循环很高效,循环次数为长度(count)的一半,而且使用>>位移运算,交换数组value[j]与value[n-j]的值。这里一是循环次数少,而是使用最高效的位移运算所以说这个reverse很高效。在反转过程中还完成了一件事:就是为hasSurrogate赋值。赋值的依据就是value[j]与value[n-j]两个字符时候有一个在\uD800和\uDFFF之间,如果有则赋值为true。

而hasSurrogate的值作为下面一个if分支的依据,如果为true,则从头到尾循环一遍。至于为何要判断hasSurrogate,以及下面一个循环的意义,请移步这里: 

 

其实到这里应该已经结束了,在我整理StringBuffer和StringBuilder结构图时发现(“刨祖坟”行家啊),发现它们两个又再次实现了CharSequence接口,为何说再次呢,因为AbstractStringBuilder已经实现了一次,不知何为!经过几个人讨论,结果还要请您再次移步这里: 

 

如果不对,有知道它们底细的,要通知我哦。

以上就是我在阅读StringBuffer和StringBuilder的收获,与大家分享。

 

 

 

 

相信大家看到过很多比较String和StringBuffer区别的文章,也明白这两者的区别,然而自从Java 5.0发布以后,我们的比较列表上将多出一个对象了,这就是StringBuilder类。String类是不可变类,任何对String的改变都会引发新的String对象的生成;而StringBuffer则是可变类,任何对它所指代的字符串的改变都不会产生新的对象,可变和不可变类这一对对象已经齐全了,那么为什么还要引入新的StringBuilder类干吗?相信大家都有此疑问,我也如此。下面,我们就来看看引入该类的原因

为什么会出现那么多比较String和StringBuffer的文章?

      原因在于当改变字符串内容时,采用StringBuffer能获得更好的性能。既然是为了获得更好的性能,那么采用StringBuffer能够获得最好的性能吗?

      答案是NO!

      为什么?

      如果你读过《Think in Java》,而且对里面描述HashTable和HashMap区别的那部分章节比较熟悉的话,你一定也明白了原因所在。对,就是支持线程同步保证线程安全而导致性能下降的问题。HashTable是线程安全的,很多方法都是synchronized方法,而HashMap不是线程安全的,但其在单线程程序中的性能比HashTable要高。StringBuffer和StringBuilder类的区别也在于此,新引入的StringBuilder类不是线程安全的,但其在单线程中的性能比StringBuffer高。如果你对此不太相信,可以试试下面的例子:

package com.demo.test;import java.util.ArrayList;import java.util.Iterator;import java.util.List;/** * @author: chengtai.he * @created:2009-12-9 上午09:59:57 */public class Test {	private static final String base = " base string. ";	private static final int count = 2000000;	public static void stringTest() {		long begin, end;		begin = System.currentTimeMillis();		String test = new String(base);		for (int i = 0; i < count / 100; i++) {			test = test + " add ";		}		end = System.currentTimeMillis();		System.out.println((end - begin) + " millis has elapsed when used String. ");	}	public static void stringBufferTest() {		long begin, end;		begin = System.currentTimeMillis();		StringBuffer test = new StringBuffer(base);		for (int i = 0; i < count; i++) {			test = test.append(" add ");		}		end = System.currentTimeMillis();		System.out.println((end - begin) + " millis has elapsed when used StringBuffer. ");	}	public static void stringBuilderTest() {		long begin, end;		begin = System.currentTimeMillis();		StringBuilder test = new StringBuilder(base);		for (int i = 0; i < count; i++) {			test = test.append(" add ");		}		end = System.currentTimeMillis();		System.out.println((end - begin) + " millis has elapsed when used StringBuilder. ");	}	public static String appendItemsToStringBuiler(List list) {		StringBuilder b = new StringBuilder();		for (Iterator i = list.iterator(); i.hasNext();) {			b.append(i.next()).append(" ");		}		return b.toString();	}	public static void addToStringBuilder() {		List list = new ArrayList();		list.add(" I ");		list.add(" play ");		list.add(" Bourgeois ");		list.add(" guitars ");		list.add(" and ");		list.add(" Huber ");		list.add(" banjos ");		System.out.println(Test.appendItemsToStirngBuffer(list));	}	public static String appendItemsToStirngBuffer(List list) {		StringBuffer b = new StringBuffer();		for (Iterator i = list.iterator(); i.hasNext();) {			b.append(i.next()).append(" ");		}		return b.toString();	}	public static void addToStringBuffer() {		List list = new ArrayList();		list.add(" I ");		list.add(" play ");		list.add(" Bourgeois ");		list.add(" guitars ");		list.add(" and ");		list.add(" Huber ");		list.add(" banjos ");		System.out.println(Test.appendItemsToStirngBuffer(list));	}	public static void main(String[] args) {		stringTest();		stringBufferTest();		stringBuilderTest();		addToStringBuffer();		addToStringBuilder();	}}

上面的程序结果如下:

5266 millis has elapsed when used String. 
375 millis has elapsed when used StringBuffer. 
281 millis has elapsed when used StringBuilder. 
 I   play   Bourgeois   guitars   and   Huber   banjos  
 I   play   Bourgeois   guitars   and   Huber   banjos 
从上面的结果来看,这三个类在单线程程序中的性能差别一目了然,采用String对象时,即使运行次数仅是采用其他对象的1/100,其执行时间仍然比其他对象高出25倍以上;而采用StringBuffer对象和采用StringBuilder对象的差别也比较明显,前者是后者的1.5倍左右。由此可见,如果我们的程序是在单线程下运行,或者是不必考虑到线程同步问题,我们应该优先使用StringBuilder类;当然,如果要保证线程安全,自然非StringBuffer莫属了。

除了对多线程的支持不一样外,这两个类的使用几乎没有任何差别,上面的例子就是个很好的说明。appendItemsToStringBuiler和appendItemsToStirngBuffer两个方法除了采用的对象分别为StringBuilder和StringBuffer外,其他完全相同,而效果也完全相同。

转载于:https://my.oschina.net/AaronDMC/blog/755848

你可能感兴趣的文章
你真的知道自己每天都需要做什么吗?
查看>>
c2java select algorithm
查看>>
Java Runtime.exec
查看>>
神经网络第一步,手写数字识别的例子分享给大家
查看>>
MobX响应式编程库
查看>>
Gradle基本使用(1):安装、IDEA使用
查看>>
Linux查看用户及其权限管理
查看>>
Kentico中的skin.css的加载
查看>>
elasticsearch6.3.1 安装以及配置IK 使用
查看>>
闪聊的beta版推出了
查看>>
WCF光芒下的Web Service
查看>>
GnuPG笔记
查看>>
批处理常用命令总结2
查看>>
ubuntu双网卡bonding配置(转)
查看>>
Ubuntu 14.04 关于 TensorFlow 环境的配置
查看>>
漂亮灵活设置的jquery通知提示插件toastr
查看>>
java多线程系类:基础篇:08之join
查看>>
TableView编辑状态下跳转页面的崩溃处理
查看>>
c#.net常用的小函数和方法集
查看>>
微软能否撑起Silverlight的明天?
查看>>