博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
fdsf
阅读量:5343 次
发布时间:2019-06-15

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

首先我们来看一段程序

package more;/** * 测试string StringBuffer StringBuilder的性能 * @author ybsem * */ public class SSbSb { public static void main(String[] args) { String string = "s"; StringBuilder stringBuilder = new StringBuilder("s"); StringBuffer stringBuffer = new StringBuffer("s"); Long startString = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { string = string + i; } Long endString = System.currentTimeMillis(); System.out.println("创建string常量时间为:"+(endString-startString)); Long startStringBuilder = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { stringBuilder = stringBuilder.append(i); } Long endStringBuilder =System.currentTimeMillis(); System.out.println("创建stringBuilder对象时间为:"+(endStringBuilder-startStringBuilder)); Long startStringBuffer = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { stringBuffer = stringBuffer.append(i); } Long endStringBuffer = System.currentTimeMillis(); System.out.println("创建stringBuffer对象时间为:"+(endStringBuffer-startStringBuffer)); } }

 

测试结果

由此我们可以从性能上看出String<StringBuffer<StringBuilder

现在我们从源码的角度来解读下为什么出现这种问题

String


 首先来看String的核心代码

public final class String    implements java.io.Serializable, Comparable
, CharSequence { /** The value is used for character storage. */ private final char value[];//final类型char数组 //省略其他代码…… …… }

由此可以看出一个String string = “s”其实是一个字符数组,并且是不可变的10000; i++) { string = string + i; }

这段代码其实相当于创建了一万个新的String对象,所以有大量对字符串的操作时不宜使用String

StringBuilder

 我们来看下StringBuilder的核心源码

@Override    public StringBuilder append(Object obj) {        return append(String.valueOf(obj));  //当传入的是一个对象时先将其转换为String类型 } @Override public StringBuilder append(String str) { super.append(str); //执行父类的append()方法,他的父类为AbstractStringBuilder return this; }

 

//StringBuilder的父类append()方法public AbstractStringBuilder append(String str) {        if (str == null)            return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); //扩容函数 str.getChars(0, len, value, count); //value是原字符串的值 /* * public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { * if (srcBegin < 0) { * throw new StringIndexOutOfBoundsException(srcBegin); * } * if (srcEnd > value.length) { * throw new StringIndexOutOfBoundsException(srcEnd); * } * if (srcBegin > srcEnd) { * throw new StringIndexOutOfBoundsException(srcEnd - srcBegin); * } * System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); //是一个本地 //方法用将两个字符串相连,通过数组的copy * } * */ count += len; //conut初始值为0,第一次执行append()方法时count变为字符 //串的值 return this; }

 

由上边的源码可以看出StringBuilder是通过底层数组进行连接的效率高,但同时也应该看到该段代码没有进行同步,所以在多线程环境下是不安全的

StringBuffer


再来看StringBuffer的源码

@Overridepublic synchronized StringBuffer append(String str) {         toStringCache = null; super.append(str); return this; }

 /父类的appen()的方法

public AbstractStringBuilder append(String str) {       if (str == null)            return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; } //源码与StringBuilder基本相同

StringBuffer与StringBuilder的区别在于将append()进行了同步,保证了多线程条件下的安全性,但在获得安全性的同时牺牲了性能,所以StringBuffer要比StringBuilder要快的原因

总结如下:

  • 在进行少量对字符串的操作时使用String
  • 在多线程环境下,并且对字符串操作次数较多则使用StringBuffer
  • 在单线程环境下,并且对字符串操作次数较多则使用StringBuilde

分析完性能我们再来分析下他们的构成


先来分析String的构造函数

 

/** The value is used for character storage. */    private final char value[]; /* Initializes a newly created {@code String} object so that it represents * the same sequence of characters as the argument; in other words, the * newly created string is a copy of the argument string. Unless an * explicit copy of {@code original} is needed, use of this constructor is * unnecessary since Strings are immutable. */ public String(String original) { this.value = original.value; this.hash = original.hash; }

有这段代码可知以及代码注释可知,string其实是一个数组,由于他是一个不可变类,新创建的字符串是参数字符串的副本。 所以呢除非需要这个副本,否则没必要用这个构造函数来进行创造。String类的字符串串创建时数组的大小是由字符串常量池也就是jvm来进行分配的。具体我们可以来分析下StringBuilder

先来看一段stringBuilder的构造函数

 

1    /** 2      * Constructs a string builder with no characters in it and an 3 * initial capacity of 16 characters. 4 */ 5 public StringBuilder() { 6 super(16); 7 } 8 /** 9 * Constructs a string builder with no characters in it and an 10 * initial capacity specified by the { @code capacity} argument. 11 */ 12 public StringBuilder(int capacity) { 13 super(capacity); 14 } 15 16 /** 17 * Constructs a string builder initialized to the contents of the 18 * specified string. The initial capacity of the string builder is 19 * { @code 16} plus the length of the string argument. 20 */ 21 public StringBuilder(String str) { 22 super(str.length() + 16); 23 append(str); 24 }

 

 

 

 

/**上述构造函数调用super函数     * Creates an AbstractStringBuilder of the specified capacity.     */    AbstractStringBuilder(int capacity) { value = new char[capacity]; }

 

这段代码清晰的看出他是基于数组构建的。初始大小为16,stringBuilder继承子AbstractStringBuilder。其次需要了解下他的一个很用要的方法append()方法,它是被用于字符串的连接。可以看出在连接的第一步就需要检查数组大小是否足够(不检查若连接长点的字符串必然会发生数组越界),

 

public AbstractStringBuilder append(String str) {        if (str == null)            return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; }

 

 

 

public void ensureCapacity(int minimumCapacity) {        if (minimumCapacity > 0) ensureCapacityInternal(minimumCapacity); } private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) expandCapacity(minimumCapacity); } void expandCapacity(int minimumCapacity) { 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.copyOf(value, newCapacity); }

 

检查数组大小是否足够分三步,count+len其实就是新字符床的长度(count旧字符串的长度len需要连接字符串的额长度),若最小长度也就是count+len小于0,不做任何事返回,因为这代表没有任何意义。若大于零则执行ensureCapacityInternal方法,value.length代表的是连接前的字符串的值,若新字符串也就是连接后字符串的大小大于旧字符串的长度,则代表数组大小可能不够用,进行扩容。执行expandCapacity方法,扩容后大小为length*2+2。扩容之后新申请的数组大小newCapacity小于0同时连接后字符串所需最小空间minimumCapacity也小于0,代表无法申请到足够大的空间,发生内存溢出抛出异常。若申请到改大小的空间之后执行数组的复制,返回连接后的数组,相当于新建了一个字符串的值。

StringBuffer进行复制的原理基本相同。

字符串连接时应该注意

 


若用String类型的字符串通过+进行拼接时,会创建一个新的String对象,你要知道String对象一旦创建就是不能被改变的,要达到字符串拼接的效果,就得不停创建新对象。StringBuilder直到最后sb.toString()才会创建String对象,之前都没有创建新对象,或者在超过数组代表长度的字符串时才进行字符串的创建,故从效率上来讲append()要优于+

 

转载于:https://www.cnblogs.com/boWatermelon/p/7219955.html

你可能感兴趣的文章
Linux 命令 ssh 一些错误的解决办法
查看>>
jQuery对checkbox的各种操作
查看>>
(精)题解 guP4878 [USACO05DEC] 布局
查看>>
用vue实现登录页面
查看>>
UVALive 5908 更新一下线段相交模板
查看>>
[转]开发Visual Studio风格的用户界面--MagicLibrary使用指南
查看>>
DS1-3
查看>>
RGB和HSB的转换推算
查看>>
Android 4.2蓝牙介绍
查看>>
MyBatis的底层实现原理
查看>>
如何实现基于ssh框架的投票系统的的质量属性
查看>>
SQL Server Profiler:使用方法和指标说明
查看>>
内存虚拟化
查看>>
数据库表 copy
查看>>
WayOs固件修改:内置简单拨号王消息控制器及支持安卓手机续费
查看>>
ES6_07_Symbol属性
查看>>
卷积神经网络
查看>>
bzoj 1596: [Usaco2008 Jan]电话网络
查看>>
iOS UIView动画效果 学习笔记
查看>>
设置代码块
查看>>