zoukankan      html  css  js  c++  java
  • 从源码的角度解读String、StringBuilder、StringBuffer的性能差别

    首先我们来看一段程序

    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<String>, 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的源码

    @Override
    public 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

    分析完性能我们再来详细的分析下append()方法


    先来分析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这是很可怕的,如果在构造的时候没有指定容量,那么很有可能在扩容之后占用了浪费大量的内存空间。使用StringBuilder或者StringBuffer时,尽可能准确地估算capacity,并在构造时指定,避免内存浪费和频繁的扩容及复制。扩容之后新申请的数组大小newCapacity小于0同时连接后字符串所需最小空间minimumCapacity也小于0,代表无法申请到足够大的空间,发生内存溢出抛出异常。若申请到改大小的空间之后执行数组的复制,返回连接后的数组,相当于新建了一个字符串的值。

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

    字符串连接时应该注意


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

    人真是奇怪,非得走到最后一步,是不会觉悟的。但是到了最后一步,又觉得太晚了。
  • 相关阅读:
    Android的FileOutputStream中向文本文件中写入换行符
    简单又好看的按钮,扁平化按钮。
    android常见错误之 No resource found that matches the given name
    eclipse中Android模拟器,DDMS看不到设备
    Android软件开发之盘点所有Dialog对话框大合集
    Android软件开发之TextView详解
    frameset网页彻底退出或跳转(转)
    .Net 如何限制用户登录(转)
    将上传图片文件转成二进制流再存储
    数据库链接字符串自动生成
  • 原文地址:https://www.cnblogs.com/boWatermelon/p/6433100.html
Copyright © 2011-2022 走看看