java中String、StringBuffer、StringBuilder的区别
Java里面提供了String,StringBuffer和StringBuilder三个类来封装字符串
0. 不当用法
String result = ""; for (String s : hugeArray) { result = result + s; }
不要使用String类的"+"来进行频繁的拼接,因为那样的性能极差的,应该使用StringBuffer或StringBuilder类,这在Java的优化上是一条比较重要的原则
1. String类封装的字符串是不可变的
字符串是由若干个字符线性排列组成的,String类的关键源码如下
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[];//final类型char数组 //省略其他代码…… …… }
因为有“final”修饰符,所以可以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); }
可以看到截取源码是生成了一个新的String对象返回的。
所以,在对String类型的字符串进行大量“插入”和“删除”操作时会产生大量的临时变量。
既然String类封装的字符串是不可变的,那么是否应该有一个类来封装可变数组。答案是有的,StringBuilder与StringBuffer封装类都是可变的。
2. 如何做到封装数组可变
StringBuilder与StringBuffer都继承自AbstractStringBuilder抽象类,在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型数组 } //省略其他代码…… ……
可以看到StringBuffer创建字符串对象默认的大小是16,当然也可以传入数组大小。
下面列下StringBuffer的用法
public class Test{ public static void main(String args[]){ StringBuffer sBuffer = new StringBuffer("hello"); sBuffer.append(" "); sBuffer.append("world"); System.out.println(sBuffer); } }
输出
hello world
下面看源码分析下append函数,来看下如何做到长度可变
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); }
可以看到空间扩展室友copyOf函数实现:
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;//返回“扩容”后的数组变量 }
3. StringBuilder与StringBuffer 区别
AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法,StringBuilder和StringBuffer的方法实现基本上一致,不同的是StringBuffer类的方法前多了个synchronized关键字,即StringBuffer是线程安全的。
public synchronized StringBuffer reverse() { super.reverse(); return this; } public int indexOf(String str) { return indexOf(str, 0); //存在 public synchronized int indexOf(String str, int fromIndex) 方法 }
(1)append,insert,delete方法最根本上都是调用System.arraycopy()这个方法来达到目的
(2)substring(int, int)方法是通过重新new String(value, start, end - start)的方式来达到目的。因此,在执行substring操作时,StringBuilder和String基本上没什么区别。
4. 使用场景
如果是多线程环境下涉及到共享变量的插入和删除操作,StringBuffer则是首选。如果是非多线程操作并且有大量的字符串拼接,插入,删除操作则StringBuilder是首选。毕竟String类是通过创建临时变量来实现字符串拼接的,耗内存还效率不高,怎么说StringBuilder是通过JNI方式实现终极操作的。
5. 总结
- String类型的字符串对象是不可变的,一旦String对象创建后,包含在这个对象中的字符系列是不可以改变的,直到这个对象被销毁。
- StringBuilder和StringBuffer类型的字符串是可变的,不同的是StringBuffer类型的是线程安全的,而StringBuilder不是线程安全的
- 如果是多线程环境下涉及到共享变量的插入和删除操作,StringBuffer则是首选。如果是非多线程操作并且有大量的字符串拼接,插入,删除操作则StringBuilder是首选。
- 如果要操作少量的数据,用String;单线程操作大量数据,用StringBuilder;多线程操作大量数据,用StringBuffer。