栈与堆,String和StringBuffer(二)

        关于String和StringBuffer之间的性能得先说说编译期和运行期。

        编译期,也就是在.java文件在编译成.class文件这个过程,这个是靠jdk的编译器来完成的,而运行期则是.class文件在JVM上运行的过程,当然是靠JVM了。那么从性能优化的角度,在编译期能做就不要在运行期做。

        拿一个以前看到过的例子 

                String a  = "abc";   String  b = "ab" + "c";    System.out.println(a == b);

                String a  = "abc";   String  s = "ab";  String  b = s + "c";    System.out.println(a == b);

        第一个打印出来为true, 第二个为false,这是因为第一个b的值是在编译期确定,第二个b的值是运行期才确定的。jdk的编译器有这样一个功能,在编译String b = "ab" + "c"; 时会把语句变成这样 String b = "abc"; 所以在运行期JVM执行的其实是String a = "abc"; String b = "abc";  而第二种情况在编译器时编译器并不知道这个s的值,它只能判断语法有无错误,到JVM运行的时候在检查到有变量参与运算会重新分配一个内存来存放值abc,相当于new了一个对象,而且运行期需要额外的开销当字符串的值无法预先知道的时候,在《提高String和StringBuffer性能的技》一文中已经说到,第二种情况编译器其实会把语句编译成 String  b = new StringBuffer().append(s).append("c").toString();

       在使用String对象的时候有个最常用的写法,String s = "a";  s += "b"; 也就是+=操作符,反正我以前是老这样写的,后来代码看得多了,才晓得其间的问题,这句代码就好比上面说到的第二种情况,相当于s = s+ "b"; 这句代码在运行期才确定,并且会重新new一个对象,若是在一个for循环语句中使用,那么循环几次不就会实例几个对象吗,所以说遇到这类情况通通用StringBuffer来代替,两者的性能差多少呢?《提高String和StringBuffer性能的技》上面有个测试代码,我自己测试了下

public class Test{ 
        public static void main(String[] arg){
              long first = System.currentTimeMillis();
              StringBuffer sb = new StringBuffer();
              String sRes = null;
              for(int i = 0; i < 1024*1024; i++){
                      sb.append("aaa");
              }
              sRes = sb.toString();
              long second = System.currentTimeMillis();
              System.out.println(second - first);  // 大概420毫秒左右。
    }
}

public class Test{ 
     public static void main(String[] arg){
           long first = System.currentTimeMillis();
           // StringBuffer sb = new StringBuffer();
           String sRes = null;
           for(int i = 0; i < 1024*1024; i++){
                // sb.append("aaa");
                sRes += "aaa";
           }
           //sRes = sb.toString();
            long second = System.currentTimeMillis();
            System.out.println(second - first);
            //  -_- !!!  因为这个等待时间太长,我就把它给关了,在这个等待的期间我上了趟厕所,看了会电视
            //大概有十分钟。可见两者性能的差距。
     }
}

      为什么会差这么多,决定去读读StringBuffer类的源代码,StringBuffer类基础了AbstractStringBuilder类,主要的实现都是在AbstractStringBuilder类中的,以这种实例方式  StringBuffer  sb = new  StringBuffer();  会调用StringBuffer的   

 public StringBuffer() {
         super(16);
 }

可以看出传了参数16,在AbstractStringBuilder带参实例方法中会实例一个数组,char[] value = new char[16]; 可见这个参数是用来实例char数组的。

StringBuffer的append()方法,我只说说传入对象的情况,若是传的不是String类型的对象,在StringBuffer中会将其转换为String对象,再传给父类的append()方法,父类的append方法中做了件事情:第一,判断传入对象是否为null,若是为null,则给该对象赋"null",第二,将原value数组的长度和传入对象长度求和,若总长度大于原value数组长度,则执行expandCapacity();方法,第三,通过System.arraycopy来拷贝数组,也就是将传入的对象转换为char[]数组,然后拼接到原value数组的后面。可以看到,StringBuffer类之所以能够变化长度,原因在于它内部维护了一个char[]数组,而这个expandCapacity();方法放在后面得再提一下。

StringBuffer的toString();方法,return new String(value, 0, count); 这里value就是char[],count是它的长度,可见StringBuffer从始至终只new了一个String对象,这也是它性能较优的原因。

expandCapacity()方法是AbstractStringBuilder类的方法,这个方法触发的条件是当原char[]数组里面实际字符长度加上传入对象的长度之和大于原char[]数组最大长度,即加上传入对象后 实际的字符个数已经大于了原来的数组实例化时的长度,自然要重新改变这个char[]的长度,它以什么规则来改变的呢。

 void expandCapacity(int minimumCapacity) {
          int newCapacity = (value.length + 1) * 2;
          if (newCapacity < 0) {
                    newCapacity = Integer.MAX_VALUE;
          } else if (minimumCapacity > newCapacity) {
                    newCapacity = minimumCapacity;
          }
          char newValue[] = new char[newCapacity];
          System.arraycopy(value, 0, newValue, 0, count);
          value = newValue;
 }

这里传入的参数minimumCapacity是新数组里实际字符的长度,数组的长度并不代表里面字符长度,比如说

StirngBuffer sb = new StringBuffer();  它会创建默认长度为16的char[]数组,但只时并没有传入对象,所以里面的16个字符都是空, sb.append("abcdabcdabcdabcdabcd"); 加入长度为20的字符串, 那么这个minimumCapacity 其实等于20,并不是16+20, 它会重新实例一个char[]数组,长度是原数组长度加1的2倍,若这个新长度还小于minimumCapacity ,则直接将minimumCapacity赋给新长度,这样新数组的长度就确定了。这就有个问题,当char[]数组已经相当大时,而传入对象又导致超过原长度,则会创建一个更大的数组并且对每个字符进行拷贝,很有可能造成空间的浪费,对字符的拷贝也相当耗费资源,所以当字符串长度比较大时使用带参的构造方法更合适些,StringBuffer  sb = new StringBuffer(250000);  这个长度可以根据append进去的对象总长度估计一下。 

 

  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值