`
zgqynx
  • 浏览: 1355477 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

栈与堆,String和StringBuffer(二) <转>

    博客分类:
  • java
阅读更多
关于String和StringBuffer之间的性能得先说说编译期和运行期。
原文:http://blog.csdn.net/mydream83/archive/2007/04/21/1573995.aspx
        编译期,也就是在.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进去的对象总长度估计一下。
分享到:
评论

相关推荐

    AIC的Java课程1-6章

    第3版 机械工业出版社&lt;br&gt; 教学内容和要求&lt;br&gt;知识点 重要程度 使用频度 难度&lt;br&gt;Java 入门 高 中 易&lt;br&gt;变量和运算符 高 高 中&lt;br&gt;控制...&lt;br&gt;IO和串行化 高 中 难&lt;br&gt;知识点 重要程度 使用频度 难度&lt;br&gt;&lt;br&gt;第1章...

    Java中String和StringBuffer的区别.doc

    在Java编程语言中,String和StringBuffer是两个重要的类,它们在处理文本数据时有着显著的区别。String类代表不可变的字符序列,一旦创建,其值就不能改变。这意味着每次对String对象进行修改(如拼接操作)都会创建...

    区别Java中堆与栈区别Java中堆与栈

    Java 中堆与栈的区别 Java 中的堆和栈是两个不同的内存区域,分别用于存放不同类型的数据。堆是一个运行时数据区,类的对象从中分配空间,通过new、newarray、anewarray 和 multianewarray 等指令建立,垃圾回收器...

    Java堆和栈的区别

    "Java 堆和栈的区别" Java 堆和栈是 Java 中的两种内存管理机制,它们都是 Java 用来在 RAM 中存放数据的地方。但是,它们有很多不同之处。 Java 堆是一个运行时数据区,类的对象从中分配空间。这些对象通过 new、...

    浅谈java中String与StringBuffer的不同

    Java中的String和StringBuffer是两种常用的字符串处理类,它们各自有着独特的特性和适用场景。String在内存中存储于栈中,通常被视为不可变对象,这意味着一旦创建,其内容就不能更改,每次试图修改String对象时,...

    Java机制下的栈和堆.pdf

    Java编程语言中,内存管理是其核心特性之一,主要分为栈内存和堆内存两部分。栈内存主要存储基本类型变量...合理利用栈内存的高效性和堆内存的灵活性,以及了解如何正确处理String对象,是编写高质量Java代码的基础。

    Java中堆和栈的区别

    Java中的堆和栈是两种不同的内存区域,它们在程序运行时承担着不同的职责。栈主要用于存储基本类型变量和对象的引用,而堆则用于存储对象实例。了解它们的区别对于优化程序性能至关重要。 栈(Stack): 1. 栈是...

    Java经典常识,绝对经典的常识,有堆栈问题,String。。。类,接口 算法。。。。

    1. **堆与栈**:在Java中,内存分为堆内存(Heap)和栈内存(Stack)。栈主要用于存储基本类型变量和对象引用,执行速度快,但容量有限。堆则用于存储对象实例,它的大小可动态扩展,但访问速度相对较慢。理解它们的...

    java内存分配和String类型的深度解析Java开发J

    Java内存主要分为三个区域:栈(Stack)、堆(Heap)和方法区(Method Area)。栈用于存储基本类型变量和对象引用,每个线程都有自己的独立栈空间。当创建一个方法时,栈会为该方法分配一块内存,用于存储局部变量。...

    String对象的内存分析

    本篇文章将深入探讨`String`对象的内存分析,包括栈、堆、常量池以及静态存储的概念,并通过具体的示例来解析不同情况下`String`对象的创建和内存分配。 首先,了解Java内存的基本结构。Java内存分为以下几个区域:...

    我们一起学Java之String

    Java中的String类是一个重要的数据类型,用于存储和操作字符串数据。String类的特点是不可变性,也就是说,一旦String对象被创建,其值就无法改变。这一特性对于Java内存管理有着重要影响,因为String对象会存储在...

    java中堆和栈的区别分析

    在Java编程语言中,堆和栈是两种主要的内存区域,它们各自有不同的功能和特点。了解它们的区别对于优化程序性能和避免内存泄漏至关重要。 **堆(Heap)** 堆是Java运行时数据区的一部分,主要用于存储对象实例。当...

    Java初级开发技术面试题.docx

    其面试题涵盖了多个方面,本文将从 String 和 StringBuffer、StringBuilder 的区别、Java 的堆、栈、方法区、==、toString 方法和 equals 方法、ArrayList 和 LinkedList 的区别、Http 协议与 TCP 协议的简单理解等...

    Java 应届生面试题

    * 基本数据类型和引用类型的区别:基本数据类型是分配在栈上的,而引用类型是分配在堆上的。 二、面向对象的特征 * 封装:方便调用类库里面已经写好的函数。 * 继承:方便对已有函数的功能进行直接调用和扩展。 * ...

    Java工程师必会100题

    方法区用于存储类信息,堆用于存储对象实例,虚拟机栈用于存储方法调用和局部变量,本地方法栈用于存储非Java方法信息,程序计数器用于记录当前线程执行的指令地址。 4. ==与equals的区别 ==是一个运算符,用于...

    面试-Java基本知识点1

    例如,ArrayList&lt;E&gt;中的E就是泛型参数。 6. 反射 反射允许程序在运行时动态地获取类的信息(如类名、方法名、属性等)并调用。它是Java强大功能的体现,但使用不当可能引发安全问题。 7. 代理 Java代理包括静态...

    计算机二级java基础试题.pdf

    List&lt;String&gt; list = Arrays.asList("a", "b", "c"); Collections.sort(list, (s1, s2) -&gt; s1.compareTo(s2)); ``` **3. 注解的使用** - **元注解**:用于标注其他注解的注解,如`@Retention`、`@Target`。 - ...

    java笔试面试总结

    - **泛型**:了解泛型的基本用法,如List&lt;String&gt;、Map&lt;String, Integer&gt;等。 7. **字符串处理** - **String对象不可变性**:理解String对象的创建与比较,以及StringBuilder与StringBuffer的异同。 - **常用...

Global site tag (gtag.js) - Google Analytics