intern

来源:互联网 发布:啪伪娘什么感觉知乎 编辑:程序博客网 时间:2024/05/22 09:46

advantage of using intern:

when you need speed since you can compare strings by reference (== is faster than equals)

disadvangae:

The primary disadvantage is that you have to remember to make sure that you actually do intern() all of the strings that you're going to compare. It's easy to forget to intern() all strings and then you can get confusingly incorrect results. Also, for everyone's sake, please be sure to very clearly document that you're relying on the strings being internalized.(一致性)

The second disadvantage if you decide to internalize strings is that the intern() method is relatively expensive. It has to manage the pool of unique strings so it does a fair bit of work (even if the string has already been internalized). So, be careful in your code design so that you e.g., intern() all appropriate strings on input so you don't have to worry about it anymore.(代价)

Third disadvantage: interned strings can't be garbage collected, so it's a potential for a memory leak.(内存泄露)


http://renxiangzyq.iteye.com/blog/549554

1. 首先String不属于8种基本数据类型,String是一个对象。   因为对象的默认值是null,所以String的默认值也是null;但它又是一种特殊的对象,有其它对象没有的一些特性。   2. new String()和new String(“”)都是申明一个新的空字符串,是空串不是null;   3. String str=”kvill”; String str=new String (“kvill”);的区别:   在这里,我们不谈堆,也不谈栈,只先简单引入常量池这个简单的概念。   常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。   看例1: String s0=”kvill”; String s1=”kvill”; String s2=”kv” + “ill”; System.out.println( s0==s1 ); System.out.println( s0==s2 );    结果为: true true    首先,我们要知道Java会确保一个字符串常量只有一个拷贝。   因为例子中的s0和s1中的”kvill”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而”kv”和”ill”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中”kvill”的一个引用。   所以我们得出s0==s1==s2;   用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。   看例2: String s0=”kvill”; String s1=new String(”kvill”); String s2=”kv” + new String(“ill”); System.out.println( s0==s1 ); System.out.println( s0==s2 ); System.out.println( s1==s2 );    结果为: false false false    例2中s0还是常量池中”kvill”的应用,s1因为无法在编译期确定,所以是运行时创建的新对象”kvill”的引用,s2因为有后半部分new String(“ill”)所以也无法在编译期确定,所以也是一个新创建对象”kvill”的应用;明白了这些也就知道为何得出此结果了。   4. String.intern():   再补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;看例3就清楚了   例3: String s0= “kvill”; String s1=new String(”kvill”); String s2=new String(“kvill”); System.out.println( s0==s1 ); System.out.println( “**********” ); s1.intern(); s2=s2.intern(); //把常量池中“kvill”的引用赋给s2 System.out.println( s0==s1); System.out.println( s0==s1.intern() ); System.out.println( s0==s2 );    结果为: false ********** false //虽然执行了s1.intern(),但它的返回值没有赋给s1 true //说明s1.intern()返回的是常量池中”kvill”的引用 true    最后我再破除一个错误的理解:   有人说,“使用String.intern()方法则可以将一个String类的保存到一个全局String表中,如果具有相同值的Unicode字符串已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中“如果我把他说的这个全局的String表理解为常量池的话,他的最后一句话,“如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的:   看例4: String s1=new String("kvill"); String s2=s1.intern(); System.out.println( s1==s1.intern() ); System.out.println( s1+" "+s2 ); System.out.println( s2==s1.intern() );    结果: false kvill kvill true    在这个类中我们没有声名一个”kvill”常量,所以常量池中一开始是没有”kvill”的,当我们调用s1.intern()后就在常量池中新添加了一个”kvill”常量,原来的不在常量池中的”kvill”仍然存在,也就不是“将自己的地址注册到常量池中”了。   s1==s1.intern()为false说明原来的“kvill”仍然存在;   s2现在为常量池中“kvill”的地址,所以有s2==s1.intern()为true。   5. 关于equals()和==:   这个对于String简单来说就是比较两字符串的Unicode序列是否相当,如果相等返回true;而==是比较两字符串的地址是否相同,也就是是否是同一个字符串的引用。   6. 关于String是不可变的  这一说又要说很多,大家只要知道String的实例一旦生成就不会再改变了,比如说:String str=”kv”+”ill”+” “+”ans”; 就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,然后”kvill”又和” “ 生成 ”kvill “存在内存中,最后又和生成了”kvill ans”;并把这个字符串的地址赋给了str,就是因为String的“不可变”产生了很多临时变量,这也就是为什么建议用StringBuffer的原因了,因为StringBuffer是可改变的

http://blog.csdn.net/xieyuooo/article/details/6859160

其实本文非常简单,不过有很多朋友经常问,网上很多例子也写个大概,很多人也只是知道和大概,就本文而来读起来非常的轻松,不过算是一些小技巧;但是我们的程序中相信用得最多的就是char数组和byte[]数组,而String就是由char[]数组组成的,一般情况下我们就可以认为String用得是最多的对象之一。


有关Sring的空间利用率方面,这里不想多说,只能说很低很低,尤其是你定义的String长度很短的时候,简直利用率不好说;在前序的一篇文章中说明了关于java的对象空间申请方法以及对象在JVM内部如何做对其的过程,就能较为明确的知道一个String是多么的浪费空间;本文就不在多提及这方面的问题了。


再谈及到String与StringBuffer和StringBuilder的区别时,前面一篇文章中将他们循环做了一系列的性能对比,发现StringBuilder性能最高,大家都知道用StringBuilder来用了,但是要明白细节才是最好的;简单来讲String是不可变的字符串,而StringBuffer和StringBuilder是可变的字符串对象,而StringBuffer是在进行内容修改时(即char数组修改)会进行线程同步操作,在同步过程中存在征用加锁和访问对象的过程,开销较大,在方法内定义的局部变量中没有必要同步,因为就是当前线程使用,所以StringBuilder为一个非同步的可变字符串对象。


OK,我们介绍了基本的概念,可以回到正题了;那么String到底是一个神马东西,通过前面的对象结构来看,首先根据String内部的定义,应该有以下内容:一个char数组指针指向一个数组对象(数组对象也是一个对象,和普通对象最大的区别需要一个位置来记录数组的长度)、offset、count、hash、serialVersionUID(这个不用计算在对象的大小中,因为在JVM启动时就会被装入到方法区中)。其次,还有对象对其的过程,而String的内容为char数组引用,指向的数组对象的内部的内容,也就是一个String相当于就包含了两个对象,两个对象都有头部,以及对其方式,数组头部会多一个保存数组长度的区域,头部还会存储对象加锁状态、唯一标识、方法区指针、GC中的Mark标志等等相应的内容,如果头部存储空间不够就会在外部开辟一个空间来存储,内部用一个指针指向那块空间;另外对象会按照8byte对其方法进行对其,即对象大小不是8byte的倍数,将会填充,方便寻址。


String经常说是不可变的字符串,但是我个人并不习惯将他说成是常量,而很多人也对String字符串不可变以及StringBuilder可变有着很多疑惑之处,String可以做+,为什么说它不可变呢?String的+到底做了什么?有人说String还有一些内容可能会放在常量池,这是什么东西?常量池和常量池的字符串拼接结果是什么(我曾在网上看到有人写常量池中字符串和常量池中字符串拼接结果还在常量池,其实未必,后面我们用事实来说话)?


当你对上述问题了如指掌,String你基本了解得有点通透了;OK,在解释这个问题之前,我们先说明一个在Hotspot自从分代JVM产生后到目前为止(G1还没有正式出来之前)不变的道理就是,当你在程序中只要使用了new关键字或者通过任何反射机制实例化的任何对象都将首先放在堆当中,当然一般情况下首先是放在Eden空间中(在一些细节的版本中会有一些区别,如启动了TABL、或对象超过指定大小直接进入Old或对象连Eden也放不下也会直接进入Old);这是不用说的事实,总之目前我们只要知道它肯定是在堆当中的就可以了。


我们先来看一段非常非常简单的代码如下所示:

[java] view plaincopy
  1. public class StringTest {  
  2.   
  3.     public static void main(String[] args) {  
  4.         String a = "abc";  
  5.         String b = "def";  
  6.           
  7.         String c = a + b;  
  8.         String d = "abc" + "def";  
  9.           
  10.         String e = new String("abc");  
  11.           
  12.         System.out.println(a == e);  
  13.         System.out.println(a.equals(e));  
  14.         System.out.println(a == "abc");  
  15.         System.out.println(a == e.intern());  
  16.         System.out.println(c == "abcdef");  
  17.         System.out.println(d == "abcdef");  
  18.     }  
  19. }  

请在没有在java上运行前猜猜结果是多少,然后再看结果。



结果如下:

false
true
true
true
false
true


如果你的结果不是猜得,而是直接自己通过理解得到的,后面的文章你就不用看了,对你来说应该没有多大意义,如果你某一个结果说得不对,或者是自己瞎猜出来的,OK,后文可能会对你的理解造成一些影响。


我们首先解释前面4个结果,再解释最后2个结果;前4个其实在前面的文章中已经说过他们的区别,不过为了方便文本继续向下说明,这里再说明一次,首先String a = "abc"这样的申请,会将对象放入常量池中,也就是放在Perm Geration中的,而String e = new String("abc")这个对象是放在Eden空间的,所以当使用a == e发生地址对比,两者肯定结果是不一样的;而当发生a == "abc"两个地址是一样的,都是指向常量池的对应对象的首地址;而equals是对比值不用多说,肯定是一样的;a == e.intern()为什么也是true呢,就是当intern()这个方法发生时,它会在常量池中寻找和e这个字符串等值的字符串(匹配的方法为equals),如果没有发现则在常量池申请一个一样的字符串对象,并将对象首地址范围,如果发现了则直接范围首地址;而a是常量池中的对象,所以e在常量池中就能找到的地址就是a的首地址;关于这个问题就不多阐述了,也有相关的很多说明,下面说下后面两个结果;算是较为神奇的结果,也是另很多人纳闷的结果,不过不用着急,说完后就很简单了。


后面两个结果一个是a指向常量池的“abc”,b指向常量池中的“def”,c是通过a和b相加,两个都是常量池对象;而d是直接等价于“abc”+“def”按照道理说,两个也是常量池对象,为什么两个对象和常量池的“abcdef”比较的结果不一样呢?(关于他们为什么是在常量池就不多说了,上面那一段已经有结果了);我们不管怎么样,首先秒杀掉一句话就是:常量池的String+常量池String结果还在常量池,这句话是不正确的,或者你的测试用例正好是后者,那么你中招了,很多事情只是通过测试也未必能得出非常有效的结果,但是较为全面的测试会让我们得出更多的结论,看看我们两种几乎一摸一样的测试,但是结果竟然是不一样的;简单说结果是前者的对象结果不是在常量池中(记住,常量池中同一个字符串肯定是唯一的),后者的结果肯定在常量池;为什么,不是我说的,是Hotspot VM告诉我的,我们做一个简单的小实验,就知道是为什么了,首先将代码修改成这样:

[java] view plaincopy
  1. public class StringTest {  
  2.   
  3.     public static void main(String[] args) {  
  4.         String a = "abc";  
  5.         String b = "def";  
  6.           
  7.         String c = a + b;  
  8.     }  
  9. }  

我们看看编译完成后它是个什么样子:

C:\>javac StringTest.java

C:\>javap -verbose StringTest

[java] view plaincopy
  1. Compiled from "StringTest.java"  
  2. public class StringTest extends java.lang.Object  
  3.   SourceFile: "StringTest.java"  
  4.   minor version: 0  
  5.   major version: 50  
  6.   Constant pool:  
  7. const #1 = Method       #9.#18//  java/lang/Object."<init>":()V  
  8. const #2 = String       #19;    //  abc  
  9. const #3 = String       #20;    //  def  
  10. const #4 = class        #21;    //  java/lang/StringBuilder  
  11. const #5 = Method       #4.#18//  java/lang/StringBuilder."<init>":()V  
  12. const #6 = Method       #4.#22//  java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  
  13. const #7 = Method       #4.#23//  java/lang/StringBuilder.toString:()Ljava/lang/String;  
  14. const #8 = class        #24;    //  StringTest  
  15. const #9 = class        #25;    //  java/lang/Object  
  16. const #10 = Asciz       <init>;  
  17. const #11 = Asciz       ()V;  
  18. const #12 = Asciz       Code;  
  19. const #13 = Asciz       LineNumberTable;  
  20. const #14 = Asciz       main;  
  21. const #15 = Asciz       ([Ljava/lang/String;)V;  
  22. const #16 = Asciz       SourceFile;  
  23. const #17 = Asciz       StringTest.java;  
  24. const #18 = NameAndType #10:#11;//  "<init>":()V  
  25. const #19 = Asciz       abc;  
  26. const #20 = Asciz       def;  
  27. const #21 = Asciz       java/lang/StringBuilder;  
  28. const #22 = NameAndType #26:#27;//  append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  
  29. const #23 = NameAndType #28:#29;//  toString:()Ljava/lang/String;  
  30. const #24 = Asciz       StringTest;  
  31. const #25 = Asciz       java/lang/Object;  
  32. const #26 = Asciz       append;  
  33. const #27 = Asciz       (Ljava/lang/String;)Ljava/lang/StringBuilder;;  
  34. const #28 = Asciz       toString;  
  35. const #29 = Asciz       ()Ljava/lang/String;;  
  36.   
  37. {  
  38. public StringTest();  
  39.   Code:  
  40.    Stack=1, Locals=1, Args_size=1  
  41.    0:   aload_0  
  42.    1:   invokespecial   #1//Method java/lang/Object."<init>":()V  
  43.    4:   return  
  44.   LineNumberTable:  
  45.    line 20  
  46.   
  47.   
  48. public static void main(java.lang.String[]);  
  49.   Code:  
  50.    Stack=2, Locals=4, Args_size=1  
  51.    0:   ldc     #2//String abc  
  52.    2:   astore_1  
  53.    3:   ldc     #3//String def  
  54.    5:   astore_2  
  55.    6:   new     #4//class java/lang/StringBuilder  
  56.    9:   dup  
  57.    10:  invokespecial   #5//Method java/lang/StringBuilder."<init>":()V  
  58.    13:  aload_1  
  59.    14:  invokevirtual   #6//Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  
  60.    17:  aload_2  
  61.    18:  invokevirtual   #6//Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  
  62.    21:  invokevirtual   #7//Method java/lang/StringBuilder.toString:()Ljava/lang/String;  
  63.    24:  astore_3  
  64.    25:  return  
  65.   LineNumberTable:  
  66.    line 70  
  67.    line 83  
  68.    line 106  
  69.    line 1325  
  70.   
  71.   
  72. }  



说明(这里不解释关于栈的计算指令,只说明大概意思):首先看到使用了一个指针指向一个常量池中的对象内容为“abc”,而另一个指针指向“def”,此时通过new申请了一个StringBuilder(jdk 1.5以前是StringBuffer),然后调用这个StringBuilder的初始化方法;然后分别做了两次append操作,然后最后做一个toString()操作;可见String的+在编译后会被编译为StringBuilder来运行(关于为什么性能还是比StringBuilder慢那么多,文章后面来说明),我们知道这里做了一个new StringBuilder的操作,并且做了一个toString的操作,前面我们已经明确说明,凡是new出来的对象绝对不会放在常量池中;toString会发生一次内容拷贝,但是也不会在常量池中,所以在这里常量池String+常量池String放在了堆中;而下面这个后面那种情况呢,我们也用同样的方式来看看结果是什么,代码更简单了:

[java] view plaincopy
  1. public class StringTest {  
  2.   
  3.     public static void main(String[] args) {  
  4.         String d = "abc" + "def";  
  5.     }  
  6. }  

看下结果:

C:\>javac StringTest.java

C:\>javap -verbose StringTest

Compiled from "StringTest.java"
public class StringTest extends java.lang.Object
  SourceFile: "StringTest.java"
  minor version: 0
  major version: 50
  Constant pool:
const #1 = Method       #4.#13; //  java/lang/Object."<init>":()V
const #2 = String       #14;    //  abcdef
const #3 = class        #15;    //  StringTest
const #4 = class        #16;    //  java/lang/Object
const #5 = Asciz        <init>;
const #6 = Asciz        ()V;
const #7 = Asciz        Code;
const #8 = Asciz        LineNumberTable;
const #9 = Asciz        main;
const #10 = Asciz       ([Ljava/lang/String;)V;
const #11 = Asciz       SourceFile;
const #12 = Asciz       StringTest.java;
const #13 = NameAndType #5:#6;//  "<init>":()V
const #14 = Asciz       abcdef;
const #15 = Asciz       StringTest;
const #16 = Asciz       java/lang/Object;

{
public StringTest();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return
  LineNumberTable:
   line 2: 0


public static void main(java.lang.String[]);
  Code:
   Stack=1, Locals=2, Args_size=1
   0:   ldc     #2; //String abcdef
   2:   astore_1
   3:   return

  LineNumberTable:
   line 11: 0
   line 13: 3


}

这下看下可能有人一下通透了,可能有人觉得更加模糊了,怎么编译完后比前面那个少那么多,是的,就是少那么多,因为当发生“abc” + “def”在同一行发生时,JVM在编译时就认为这个加号是没有用处的,编译的时候就直接变成成

[java] view plaincopy
  1. String d = "abcdef";  

同理如果出现:String a = "a" + 1,编译时候就会变成:String a = "a1";

再例如:

[java] view plaincopy
  1. final String a = "a";  
  2. final String b = "ab";  
  3. String c = a + b;  

在编译时候,c部分会被编译为:String c = "aab";但是如果a或b有任意一个不是final的,都会new一个新的对象出来;其次再补充下,如果a和b,是某个方法返回回来的,不论方法中是final类型的还是常量什么的,都不会被在编译时将数据编译到常量池,因为编译器并不会跟踪到方法体里面去看你做了什么,其次只要是变量就是可变的,即使你认为你看到的代码是不可变的,但是运行时是可以被切入的。

就是这么简单,运行时自然直接就在常量池中是一个对象了,而不需要每次访问到这里做一个加法操作,有引用的时候,JVM不确定你要拿引用去做什么,所以它并不会直接将你的字符串进行编译时的合并(其实在某些情况下JVM可以适当考虑合并,但是JVM可能是考虑到编译时优化的算法复杂性,所以这些优化可能会放在运行时的JIT来完成,但JIT优化这部分java代码是有一些前提条件的)


所以并不是常量池String+常量池String结果还在常量池,而是编译时JVM就认为他们没有必要做,直接合并了,就像JVM做if(true)和if(false)的优化一样的道理,而前者如果是引用给出来的常量池对象,JVM在拼接过程中是通过申请StringBuilder来完成的,也就是它的结果就像普通对象一样放在堆当中的。


好了,反过来一切都很明了了,String为什么不可变,因为+操作是新申请了对象;+到底做了什么,是申请了一个StringBuilder来做append操作,然后再toString成一个新的对象;如果不是new出来的字符串或者是通过.intern()得到的字符串,则是常量池中的对象;常量池中的字符串和常量池中的字符串拼接,他们的结果不一定还在常量池,如果还在常量池只有一种可能性就是编译时就合并了,因为运行时new出来的StringBuilder是不可能放在常量池中的,我们绝大部分字符串拼接都是有引用的,而不是直接两个常量串来做的。


下面回顾最后一个问题就是,既然String拼接是通过StringBuilder来完成的,那么为什么String的+和StringBuilder会有那么大的差距呢?这是一个值得考虑的问题,如果String的+操作和StringBuilder是一样的操作,那么我们的StringBuilder就没有多大存在的必要了,因为apend太多字符串是一件非常恶心的事情。


首先你会发现,如果在同一条代码中(不一定是同一行代码,因为java代码可以相互包装嵌套,指对于成来讲基本的一条代码),

如String a = a + b + c;这条代码算是同一行,而System.out.println(a + b + c + String.format(d , "[%s]"));对于d就会单独处理后,再和a + b+ c处理,然后再调用System中的静态成员out对象中的println方法;

回到正题,对于同一条代码中,如果发生这种加法操作(不是编译时合并的),那么你在通过javap命令分析时会发现,他们的结果回将其申请一个StringBuilder然后进行append,不论多少个字符串都会append,然后最后toString()操作,这就纳闷了,为什么性能差距会那么大(在循环次数越多的时候差距会越来越大),最终没办法,我们用多行和循环测试,又看了下两者之间的区别,在使用String做+操作时,如果是多条代码或者在循环中做的话,每条代码都会做一个新的new StringBuilder,然后最后会toString一下,也就是当两个字符串相加时,会“最少”多申请一个StringBuilder然后再转换为一个String(虽然是将StringBuilder中内容拷贝到一个新的String中,但是空间是两块),所以浪费空间比较快,而且如果字符串越长,循环的过程中就会逐步进入old,而且old中的东西也会越来越多,导致了疯狂的GC,最后会疯狂的Full GC,再多的内存也会很快达到Full GC,只要你做循环;其实在常规应用中,一般你只需要做几行的字符串叠加也无所谓,如果能写成一行就写成一行,如果非要写成多行还想要性能的话,就用StringBuilder吧;其实快并不是在多少申请了对象,因为java申请对象的速度非常快速,不存在说因为多申请了两个对象就会导致什么大的问题,大的问题是因为这些临时空间所产生的垃圾,最终导致了疯狂的GC,上述两种情况在做多次循环的过程中本地使用代码:-XX:+PrintGCDetails来运行,你会发现,使用String做加法,刚开始会疯狂的YGC,过一段后会疯狂的FullGC,最后内存溢出,而使用StringBuilder几乎不会做GC,要做应该是做YGC,如果发生FGC一般说明这个字符串已经快把OLD区域撑满了,也就说马上要内存溢出了,而前者临时对象也应该去掉的,但是它会比StringBuilder叠加次数更少的时候,发生内存溢出,那是因为对象比较大的时候,临时对象已经在old区域,而前一个临时对象正好是要作为后一个对象的拷贝,所以在后面那个对象还没有拷贝成功前,前面那个对象的空间还不能被释放,那么很明显,old区域的利用率一般到一半的时候就溢出了。


最后补充一个话题,其实StringBuilder也有一些问题,就是在动态扩容的过程中,每次增加2倍的空间,并不是在原有空间上做类似的C语言的realloc操作,而是新申请一个2倍大小的空间,将这些内容再拷贝过去;StringBuilder之所以可以动态增加是因为一个预先分配的char长度,如果没有满可以继续在后面添加内容,如果满了就申请一个2倍的空间,然后将前面的拷贝过去;不难说出两个问题,所谓的动态扩容只是逻辑上的实现,而并非真正的动态扩容,这也有它的内存安全性考虑,而String是多长,数组的长度就多长(注意:这个长度和前面说的对象大小关系并不大,对象大小前面有一定的介绍);另一个可以看出的问题就是动态扩容的过程中同样会产生各种各样的垃圾对象,其实在循环的过程中,看得往往还没有那么明显,在多线程访问多个随机方法,每个随机方法内部都会去做一些apend,而且都大于10的时候,临时对象就多了;不过还好,它的临时对象只是char数组,而不是String对象,前面说了,String对象相当于两个对象,前面那个对象的大小也是很大的;但是如果你需要考虑这样的细节,那么请在编写StringBuilder的时候,预先写好你认为它可能的最大长度,尤其是被反复调用的代码,如StringBuilder builder = new StringBuilder(2048);一般的小对象没有必要这样做,而且一次申请对象如果过大可能很容易进入old区域,甚至于直接进入old区域,这是我们不想看到的;但是这种方法就要求每一位程序员都要有非常高的素质和修养,但是大多数的程序员你可能叫他写StringBuilder就够意思了,呵呵,更加不要说叫他去些意思了,那么这个办法并不能让所有的程序员所接受,目前的Hotspot还未解决这个问题,但是JRockit已经有一种解决方案了,它的解决方案很好的一种方法,就是在编译时它就能决定在这个局部方法内部你会发生多少次的append操作,那么它的StringBuilder内部做的就不是char数组,而是一个String[],预先分配数组的长度就是和append次数一样大小的数组,每做一次append就像数组下标增加1,并且放在对应的数组位置,并记录下总体的长度,待这个对象发生toString操作时,此时再申请一个这个长度一样大小的char[]空间,将数据拷贝进去,就解决了所有的临时对象的问题,对于在增加了一次间接访问和toString时候发生的逐个拷贝这些开销都是可以接受的(只要append的次数不是特别的多,一般append的次数也不可能特别多,所以利用循环测试出来的性能区别这个时候也是不靠谱的);


最后,所谓的String拼接和StringBuilder下的使用,只要不是太大的字符串或者太多次数的拼接或者高并发访问的代码段做了2行代码以上的拼接,String做加法几乎和StringBuilder区别不大;太大的字符串产生的太大的临时空间,太多的拼接次数是产生太多的临时空间,同一条代码中作String的拼接(不论拼接次数)和使用StringBuilder做append效果一致,只是每次append结果在这行发生完成后会发生toString操作,而默认申请的StringBuilder大小默认为10,如果超过限制则翻倍,这也算是一个限制。


其余的就没什么了,此文闲扯,做做实验便知道,使用命令分析更加深入,关于动态扩展,在集合类里面也有类似的情况,需要注意。