详解String,StringBuffer,StringBuilder

一、通过代码测试三者进行字符串拼接的性能

S t r i n g , S t r i n g B u f f e r , S t r i n g B u i l d e r String,StringBuffer,StringBuilder String,StringBuffer,StringBuilder三者各做6000次字符串拼接,观察其分别使用的内存和时间。

/**
 * 对String,StringBuilder,StringBuffer进行字符串拼接性能比较
 */
public class String_StringBuilder_StringBuffer {
    public static void main(String[] args) {
        int nums = 6000;//进行字符串拼接的次数
        //使用String进行字符串的拼接
        String str = "";
        long memoryStart1 = Runtime.getRuntime().freeMemory();//获取系统剩余内存
        long timeStart1 = System.currentTimeMillis();//获取系统的当前时间
        for (int i = 0; i < nums; i++) {
            str = str + i;
        }
        long memoryEnd1 = Runtime.getRuntime().freeMemory();
        long timeEnd1 = System.currentTimeMillis();
        System.out.println("String占用内存:" + (memoryEnd1 - memoryStart1));
        System.out.println("String占用时间:" + (timeEnd1 - timeStart1));

        //使用StringBuilder进行字符串的拼接
        StringBuilder builder = new StringBuilder("");
        long memoryStart2 = Runtime.getRuntime().freeMemory();//获取系统剩余内存
        long timeStart2 = System.currentTimeMillis();//获取系统的当前时间
        for (int i = 0; i < nums; i++) {
            builder = builder.append(i);
        }
        long memoryEnd2 = Runtime.getRuntime().freeMemory();
        long timeEnd2 = System.currentTimeMillis();
        System.out.println("StringBuilder占用内存:" + (memoryEnd2 - memoryStart2));
        System.out.println("StringBuilder占用时间:" + (timeEnd2 - timeStart2));

        //使用StringBuffer进行字符串的拼接
        StringBuffer buffer = new StringBuffer("");
        long memoryStart3 = Runtime.getRuntime().freeMemory();//获取系统剩余内存
        long timeStart3 = System.currentTimeMillis();//获取系统的当前时间
        for (int i = 0; i < nums; i++) {
            buffer = buffer.append(i);
        }
        long memoryEnd3 = Runtime.getRuntime().freeMemory();
        long timeEnd3 = System.currentTimeMillis();
        System.out.println("StringBuffer占用内存:" + (memoryEnd3 - memoryStart3));
        System.out.println("StringBuffer占用时间:" + (timeEnd3 - timeStart3));
    }
}

输出结果:

String占用内存:188816272
String占用时间:108
StringBuilder占用内存:0
StringBuilder占用时间:0
StringBuffer占用内存:0
StringBuffer占用时间:1

从结果来看, S t r i n g String String所用的时间和占用的内存远远高于 S t r i n g B u i l d e r StringBuilder StringBuilder S t r i n g B u f f e r StringBuffer StringBuffer,下面就来分析一下原因。

二、String

为什么效率低?

在Java中,字符串属于对象,String类的字符串对象初始化后是不能够改变其中的字符的,因此每次对String类的字符串的每一次改动,都会生成新的String对象(开辟新的堆内存),所以导致效率低下,且浪费内存空间。
String字符串对象进行拼接操作的内存变化如下:
详解String,StringBuffer,StringBuilder_第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[];
public String() {
        this.value = "".value;
    }

String 类内部使用char数据来保存字符串的value,并且使用关键字final修饰。
而final实例字段必须在构造对象时初始化,并且之后不能再修改这个字段,所以String类的字符串对象是不可变的。

String的优点

每次修改字符串都需要重新创建一个新的字符串对象,那一定会降低运行效率吗?也不尽然。不可变的String类虽然不像StringBuilder和StringBuffer那样,可以直接修改代码单元,而不用创建新的对象,但String是不可变的字符串,同样有着一个很大的优点:编译器可以让字符串共享。
字符串对象在JVM中可能有两个存放的位置:字符串常量池或堆内存。

  • 使用常量字符串初始化的字符串对象,它的值存放在字符串常量池中;
  • 使用字符串构造方法创建的字符串对象,它的值存放在堆内存中。

对于存储在公共的字符串常量池中的字符串,字符串变量指向存储池中相应的位置。如果复制一个字符串变量,原始字符串和复制的字符串共享相同的字符。Java的设计者认为共享带来的高效率远远超过拼接字符串所带来的低效率,同时大多数情况下,我们只需要对字符串进行比较等操作,并不需要改变字符串。因此,String算是有利有弊吧。

三、StringBuilder和StringBuffer

和 String 类不同的,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不会产生新的对象。我们先看StringBuilder的源码来分析

@Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

从StringBuilder的append()方法看不出来什么,注意到StringBuilder是继承了AbstractStringBuilder

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

那么继续查看AbstractStringBuilder的源码

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;

    /**
     * The count is the number of characters used.
     */
    int count;

AbstractStringBuilder同样采用char数组来保存值,但是并没有用final修饰,说明AbstractStringBuilder对象是可变的。
再看append()方法

/*
* @param   str   a string.
     * @return  a reference to this object.
     */
    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;
    }

在append()方法里调用了getChars()方法,继续查看getChars()方法
getChars()方法是String类中的方法

/*
 * @param      srcBegin   index of the first character in the string to copy.
 * @param      srcEnd     index after the last character in the string to copy.
 * @param      dst        the destination array.
 * @param      dstBegin   the start offset in the destination array.
*/
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);
    }

getChars()方法继续调用System提供了一个静态方法arraycopy(),将追加append的值复制到原字符串(char数组)末尾。所以StringBuilder就是通过这样对数组的扩容、复制操作完成字符串拼接(append)。
附系统方法arraycopy()的定义:

/*
     * @param      src      the source array.
     * @param      srcPos   starting position in the source array.
     * @param      dest     the destination array.
     * @param      destPos  starting position in the destination data.
     * @param      length   the number of array elements to be copied. 
*/
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

讲完了StringBuilder,我们再来看StringBuffer
StringBuffer和StringBuilder类功能基本相似,主要区别在于StringBuffer类的方法是多线程、安全的,而StringBuilder不是线程安全的
先上源码:

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

可见 StringBuffer和StringBuilder一样,同样继承自AbstractStringBuilder。
再来看append()方法

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

和StringBuilder一样,都是调用父类AbstractStringBuilder的方法来实现,但不同的是,StringBuffer通过Synchronized给方法加了锁,是线程安全的。

四、使用总结

  • StringBuilder线程不安全,效率高(一般使用它)
  • StringBuffer线程安全,效率低,需要考虑多线程并发的时候使用
  • String类的字符串初始化后是不能够改变其中的字符的,所以使用StringBuilder和StringBuffer。当要多次往string数组后加内容时,避免使用String类 。操作少量数据或者很少改变字符串的值时,使用String。

你可能感兴趣的:(Java,字符串,java)