十七,String,StringBuffer和StringBuilder

1.StringBuffer定义

StringBuffer 是一个线程安全的可变的字符序列.它继承于AbstractStringBuilder,实现了CharSequence接口.

StringBuffer是线程安全的.在字符串拼接性能要比String字符串相加效率高.

2.StringBuffer操作

示例:

/** 
     * StringBuffer 演示程序 
     * 
     */  
    import java.util.HashMap;  
      
    public class StringBufferTest {  
      
        public static void main(String[] args) {  
            testInsertAPIs() ;  
            testAppendAPIs() ;  
            testReplaceAPIs() ;  
            testDeleteAPIs() ;  
            testIndexAPIs() ;  
            testOtherAPIs() ;  
        }  
      
        /** 
         * StringBuffer 的其它API示例 
         */  
        private static void testOtherAPIs() {  
      
            System.out.println("-------------------------------- testOtherAPIs --------------------------------");  
      
            StringBuffer sbuilder = new StringBuffer("0123456789");  
      
            int cap = sbuilder.capacity();  
            System.out.printf("cap=%d\n", cap);  
      
            char c = sbuilder.charAt(6);  
            System.out.printf("c=%c\n", c);  
      
            char[] carr = new char[4];  
            sbuilder.getChars(3, 7, carr, 0);  
            for (int i=0; i<carr.length; i++)  
                System.out.printf("carr[%d]=%c ", i, carr[i]);  
            System.out.println();  
      
            System.out.println();  
        }  
      
        /** 
         * StringBuffer 中index相关API演示 
         */  
        private static void testIndexAPIs() {  
            System.out.println("-------------------------------- testIndexAPIs --------------------------------");  
      
            StringBuffer sbuilder = new StringBuffer("abcAbcABCabCaBcAbCaBCabc");  
            System.out.printf("sbuilder=%s\n", sbuilder);  
      
            // 1. 从前往后,找出"bc"第一次出现的位置  
            System.out.printf("%-30s = %d\n", "sbuilder.indexOf(\"bc\")", sbuilder.indexOf("bc"));  
      
            // 2. 从位置5开始,从前往后,找出"bc"第一次出现的位置  
            System.out.printf("%-30s = %d\n", "sbuilder.indexOf(\"bc\", 5)", sbuilder.indexOf("bc", 5));  
      
            // 3. 从后往前,找出"bc"第一次出现的位置  
            System.out.printf("%-30s = %d\n", "sbuilder.lastIndexOf(\"bc\")", sbuilder.lastIndexOf("bc"));  
      
            // 4. 从位置4开始,从后往前,找出"bc"第一次出现的位置  
            System.out.printf("%-30s = %d\n", "sbuilder.lastIndexOf(\"bc\", 4)", sbuilder.lastIndexOf("bc", 4));  
      
            System.out.println();  
        }  
      
        /** 
         * StringBuffer 的replace()示例 
         */  
        private static void testReplaceAPIs() {  
      
            System.out.println("-------------------------------- testReplaceAPIs ------------------------------");  
      
            StringBuffer sbuilder;  
      
            sbuilder = new StringBuffer("0123456789");  
            sbuilder.replace(0, 3, "ABCDE");  
            System.out.printf("sbuilder=%s\n", sbuilder);  
      
            sbuilder = new StringBuffer("0123456789");  
            sbuilder.reverse();  
            System.out.printf("sbuilder=%s\n", sbuilder);  
      
            sbuilder = new StringBuffer("0123456789");  
            sbuilder.setCharAt(0, 'M');  
            System.out.printf("sbuilder=%s\n", sbuilder);  
      
            System.out.println();  
        }  
      
        /** 
         * StringBuffer 的delete()示例 
         */  
        private static void testDeleteAPIs() {  
      
            System.out.println("-------------------------------- testDeleteAPIs -------------------------------");  
      
            StringBuffer sbuilder = new StringBuffer("0123456789");  
              
            // 删除位置0的字符,剩余字符是“123456789”.  
            sbuilder.deleteCharAt(0);  
            // 删除位置3(包括)到位置6(不包括)之间的字符,剩余字符是“123789”.  
            sbuilder.delete(3,6);  
      
            // 获取sb中从位置1开始的字符串  
            String str1 = sbuilder.substring(1);  
            // 获取sb中从位置3(包括)到位置5(不包括)之间的字符串  
            String str2 = sbuilder.substring(3, 5);  
            // 获取sb中从位置3(包括)到位置5(不包括)之间的字符串,获取的对象是CharSequence对象,此处转型为String  
            String str3 = (String)sbuilder.subSequence(3, 5);  
      
            System.out.printf("sbuilder=%s\nstr1=%s\nstr2=%s\nstr3=%s\n",   
                    sbuilder, str1, str2, str3);  
      
            System.out.println();  
        }  
      
        /** 
         * StringBuffer 的insert()示例 
         */  
        private static void testInsertAPIs() {  
      
            System.out.println("-------------------------------- testInsertAPIs -------------------------------");  
      
            StringBuffer sbuilder = new StringBuffer();  
      
            // 在位置0处插入字符数组  
            sbuilder.insert(0, new char[]{'a','b','c','d','e'});  
            // 在位置0处插入字符数组.0表示字符数组起始位置,3表示长度  
            sbuilder.insert(0, new char[]{'A','B','C','D','E'}, 0, 3);  
            // 在位置0处插入float  
            sbuilder.insert(0, 1.414f);  
            // 在位置0处插入double  
            sbuilder.insert(0, 3.14159d);  
            // 在位置0处插入boolean  
            sbuilder.insert(0, true);  
            // 在位置0处插入char  
            sbuilder.insert(0, '\n');  
            // 在位置0处插入int  
            sbuilder.insert(0, 100);  
            // 在位置0处插入long  
            sbuilder.insert(0, 12345L);  
            // 在位置0处插入StringBuilder对象  
            sbuilder.insert(0, new StringBuffer("StringBuilder"));  
            // 在位置0处插入StringBuilder对象.6表示被在位置0处插入对象的起始位置(包括),13是结束位置(不包括)  
            sbuilder.insert(0, new StringBuffer("STRINGBUILDER"), 6, 13);  
            // 在位置0处插入StringBuffer对象.  
            sbuilder.insert(0, new StringBuffer("StringBuffer"));  
            // 在位置0处插入StringBuffer对象.6表示被在位置0处插入对象的起始位置(包括),12是结束位置(不包括)  
            sbuilder.insert(0, new StringBuffer("STRINGBUFFER"), 6, 12);  
            // 在位置0处插入String对象.  
            sbuilder.insert(0, "String");  
            // 在位置0处插入String对象.1表示被在位置0处插入对象的起始位置(包括),6是结束位置(不包括)  
            sbuilder.insert(0, "0123456789", 1, 6);  
            sbuilder.insert(0, '\n');  
      
            // 在位置0处插入Object对象.此处以HashMap为例  
            HashMap map = new HashMap();  
            map.put("1", "one");  
            map.put("2", "two");  
            map.put("3", "three");  
            sbuilder.insert(0, map);  
      
            System.out.printf("%s\n\n", sbuilder);  
        }  
      
        /** 
         * StringBuffer 的append()示例 
         */  
        private static void testAppendAPIs() {  
      
            System.out.println("-------------------------------- testAppendAPIs -------------------------------");  
      
            StringBuffer sbuilder = new StringBuffer();  
      
            // 追加字符数组  
            sbuilder.append(new char[]{'a','b','c','d','e'});  
            // 追加字符数组.0表示字符数组起始位置,3表示长度  
            sbuilder.append(new char[]{'A','B','C','D','E'}, 0, 3);  
            // 追加float  
            sbuilder.append(1.414f);  
            // 追加double  
            sbuilder.append(3.14159d);  
            // 追加boolean  
            sbuilder.append(true);  
            // 追加char  
            sbuilder.append('\n');  
            // 追加int  
            sbuilder.append(100);  
            // 追加long  
            sbuilder.append(12345L);  
            // 追加StringBuilder对象  
            sbuilder.append(new StringBuffer("StringBuilder"));  
            // 追加StringBuilder对象.6表示被追加对象的起始位置(包括),13是结束位置(不包括)  
            sbuilder.append(new StringBuffer("STRINGBUILDER"), 6, 13);  
            // 追加StringBuffer对象.  
            sbuilder.append(new StringBuffer("StringBuffer"));  
            // 追加StringBuffer对象.6表示被追加对象的起始位置(包括),12是结束位置(不包括)  
            sbuilder.append(new StringBuffer("STRINGBUFFER"), 6, 12);  
            // 追加String对象.  
            sbuilder.append("String");  
            // 追加String对象.1表示被追加对象的起始位置(包括),6是结束位置(不包括)  
            sbuilder.append("0123456789", 1, 6);  
            sbuilder.append('\n');  
      
            // 追加Object对象.此处以HashMap为例  
            HashMap map = new HashMap();  
            map.put("1", "one");  
            map.put("2", "two");  
            map.put("3", "three");  
            sbuilder.append(map);  
            sbuilder.append('\n');  
      
            // 追加unicode编码  
            sbuilder.appendCodePoint(0x5b57);    // 0x5b57是“字”的unicode编码  
            sbuilder.appendCodePoint(0x7b26);    // 0x7b26是“符”的unicode编码  
            sbuilder.appendCodePoint(0x7f16);    // 0x7f16是“编”的unicode编码  
            sbuilder.appendCodePoint(0x7801);    // 0x7801是“码”的unicode编码  
      
            System.out.printf("%s\n\n", sbuilder);  
        }  
    }


运行结果为 :

-------------------------------- testInsertAPIs -------------------------------  
    {3=three, 2=two, 1=one}  
    12345StringBUFFERStringBufferBUILDERStringBuilder12345100  
    true3.141591.414ABCabcde  
      
    -------------------------------- testAppendAPIs -------------------------------  
    abcdeABC1.4143.14159true  
    10012345StringBuilderBUILDERStringBufferBUFFERString12345  
    {3=three, 2=two, 1=one}  
    字符编码  
      
    -------------------------------- testReplaceAPIs ------------------------------  
    sbuilder=ABCDE3456789  
    sbuilder=9876543210  
    sbuilder=M123456789  
      
    -------------------------------- testDeleteAPIs -------------------------------  
    sbuilder=123789  
    str1=23789  
    str2=78  
    str3=78  
      
    -------------------------------- testIndexAPIs --------------------------------  
    sbuilder=abcAbcABCabCaBcAbCaBCabc  
    sbuilder.indexOf("bc")         = 1  
    sbuilder.indexOf("bc", 5)      = 22  
    sbuilder.lastIndexOf("bc")     = 22  
    sbuilder.lastIndexOf("bc", 4)  = 4  
      
    -------------------------------- testOtherAPIs --------------------------------  
    cap=26  
    c=6  
    carr[0]=3 carr[1]=4 carr[2]=5 carr[3]=6



3.String对象创建的引申

  • 方式一:利用构造器:String s=new String("Hello world")

  • 方式二:直接创建:String s="Hello world"

3.1引入一些必要的知识

Java class文件结构和常量池

我们都知道,Java程序要运行,首先需要编译器将源代码文件编译成字节码文件(也就是.class文件).然后在由JVM解释执行.

class文件是8位字节的二进制流.这些二进制流的涵义由一些紧凑的有意义的项组成.比如class字节流中最开始的4个字节组成的项叫做魔数 (magic),其意义在于分辨class文件(值为0xCAFEBABE)与非class文件.其中,class文件中常量池:专门放置源代码中的符号信息(并且不同的符号信息放置在不同标志的常量表中).

JVM运行class文件

源代码编译成class文件之后,JVM就要运行这个class文件.它首先会用类装载器加载进class文件.然后需要创建许多内存数据结构来存放 class文件中的字节数据.比如class文件对应的类信息数据、常量池结构、方法中的二进制指令序列、类方法与字段的描述信息等等.当然,在运行的时候,还需要为方法创建栈帧等.这么多的内存结构当然需要管理,JVM会把这些东西都组织到几个“运行时数据区”中.这里面就有我们经常说的“方法区 ”、“堆 ”、“Java栈 ”等.

上面我们提到了,Java源代码中的每一个字面值字符串,都会在编译成class文件阶段,形成标志号为8(CONSTANT_String_info)的常量表. JVM加载 class文件的时候,会为对应的常量池建立一个内存数据结构,并存放在方法区中.同时JVM会自动为CONSTANT_String_info常量表中的字符串常量字面值 在堆中创建新的String对象(intern字符串对象,又叫拘留字符串对象).然后把CONSTANT_String_info常量表的入口地址转变成这个堆中String对象的直接地址(常量池解析).

这里很关键的就是这个拘留字符串对象.源代码中所有相同字面值的字符串常量只可能建立唯一一个拘留字符串对象. 实际上JVM是通过一个记录了拘留字符串引用的内部数据结构来维持这一特性的.Java程序中,可以调用Stringintern()方法来使得一个常规字符串对象成为拘留字符串对象.我们会在后面介绍这个方法的.

3.2 操作码助忆符指令

有了上面阐述的两个知识前提,下面我们将根据二进制指令来区别两种字符串对象的创建方式:

String s=new String("Hello world");编译成class文件后的指令(myeclipse中查看)

Class字节码指令集代码

0 new java.lang.String [15] //在堆中分配一个String类对象的空间,并将该对象的地址堆入操作数栈.

3 dup //复制操作数栈顶数据,并压入操作数栈.该指令使得操作数栈中有两个String对象的引用值.

4 ldc <String "Hello world"> [17] //将常量池中的字符串常量"Hello world"指向的堆中拘留String对象的地址压入操作数栈

6 invokespecial java.lang.String(java.lang.String) [19] //调用String的初始化方法,弹出操作数栈栈顶的两个对象地址,用拘留String对象的值初始化new指令创建的String对象,然后将这个对象的引用压入操作数栈

9 astore_1 [s] // 弹出操作数栈顶数据存放在局部变量区的第一个位置上.此时存放的是new指令创建出的,已经被初始化的String对象的地址.

事实上,在运行这段指令之前,JVM就已经为"Hello world"在堆中创建了一个拘留字符串( 值得注意的是:如果源程序中还有一个"Hello world"字符串常量,那么他们都对应了同一个堆中的拘留字符串).然后用这个拘留字符串的值来初始化堆中用new指令创建出来的新的String对象,局部变量s实际上存储的是new出来的堆对象地址.大家注意了,此时在JVM管理的堆中,有两个相同字符串值的String对象:一个是拘留字符串对象,一个是new新建的字符串对象.如果还有一条创建语句String s1=new String("Hello world");堆中有几个值为"Hello world"的字符串呢? 答案是3,大家好好想想为什么吧!

②将String s="Hello world";编译成class文件后的指令

Class字节码指令集代码

0 ldc <String "Hello world"> [15]//将常量池中的字符串常量"Hello world"指向的堆中拘留String对象的地址压入操作数栈

2 astore_1 [str] // 弹出操作数栈顶数据存放在局部变量区的第一个位置上.此时存放的是拘留字符串对象在堆中的地址 .

和上面的创建指令有很大的不同,局部变量s存储的是早已创建好的拘留字符串的堆地址. 大家好好想想,如果还有一条穿件语句String s1="Hello word";此时堆中有几个值为"Hello world"的字符串呢?答案是1.那么局部变量ss1存储的地址是否相同呢? 呵呵, 这个你应该知道了吧.

总结: String类型其实也很普通.真正让她神秘的原因就在于CONSTANT_String_info常量表和拘留字符串对象的存在.

4.String操作的几个疑问

4.1关于字符串相等关系的争论

示例:

//代码1

String sa=new String("Hello world");

String sb=new String("Hello world");

System.out.println(sa==sb); // false

//代码2

String sc="Hello world";

String sd="Hello world";

System.out.println(sc==sd); // true
代码 1中局部变量 sa,sb中存储的是 JVM在堆中 new出来的两个 String对象的内存地址 .虽然这两个 String对象的值 (char[]存 放的字符序列 )都是 "Hello world". 因此 "=="比较的是两个不同的堆地址 .代码 2中局部变量 sc,sd中存储的也是地址 ,但却都是常量池中 "Hello world"指向的堆的唯一的那个拘留字符串对象的地址 .自然相等了 .

4.2字符串“+”操作的内幕

示例:

//代码1

String sa = "ab";

String sb = "cd";

String sab=sa+sb;

String s="abcd";

System.out.println(sab==s); // false

//代码2

String sc="ab"+"cd";

String sd="abcd";

System.out.println(sc==sd); //true
代码 1中局部变量 sa,sb存储的是堆中两个拘留字符串对象的地址 .而 当执行 sa+sb,JVM首先会在堆中创建一个 StringBuilder,同时用 sa指向的拘留字符串对象完成初始化 ,然后调用 append方法完成对 sb所指向的拘留字符串的合并操作 ,接着调用 StringBuildertoString()方法在堆中创建一个 String对象 ,最后将刚生成的 String对象的堆地址存放在局部变量 sab.而局部变量 s存储的是常量池中 "abcd"所对应的拘留字符串对象的地址 . sabs地址当然不一样了 .这里要注意了 ,代码 1的堆中实际上有五个字符串对象:三个拘留字符串对象、一个 String对象和一个 StringBuilder对象 .

代码2"ab"+"cd"会直接在编译期就合并成常量"abcd", 因此相同字面值常量"abcd"所对应的是同一个拘留字符串对象,自然地址也就相同.

5 String,StringBufferStringBuilder

  • String (JDK1.0) 不可变字符序列
  • StringBuffer (JDK1.0) 线程安全的可变字符序列
  • StringBuilder (JDK1.5) 非线程安全的可变字符序列

5.1 StringBufferString的可变性

我们先看看这两个类的部分源代码:

示例:

//String
public final class String
{
        private final char value[];

         public String(String original) {
              // 把原字符串original切分成字符数组并赋给value[];
         }
}

//StringBuffer
public final class StringBuffer extends AbstractStringBuilder
{
         char value[]; //继承了父类AbstractStringBuilder中的value[]
         public StringBuffer(String str) {
                 super(str.length() + 16); //继承父类的构造器,并创建一个大小为str.length()+16的value[]数组
                 append(str); //将str切分成字符序列并加入到value[]中
        }
}
很显然 ,StringStringBuffer中的 value[]都用于存储字符序列 .但是 ,

String中的是常量(final)数组,只能被赋值一次.

比如:new String("abc")使得value[]={'a','b','c'},之后这个String对象中的value[]再也不能改变了.这也正是大家常说的,String是不可变的原因 .

注意:这个对初学者来说有个误区,有人说String str1=new String("abc"); str1=new String("cba");不是改变了字符串str1吗?那么你有必要先搞懂对象引用和对象本身的区别.这里我简单的说明一下,对象本身指的是存放在堆空间中的该对象的实例数据(非静态非常量字段).而对象引用指的是堆中对象本身所存放的地址,一般方法区和Java栈中存储的都是对象引用,而非对象本身的数据.

StringBuffer中的value[]就是一个很普通的数组,而且可以通过append()方法将新字符串加入value[]末尾.这样也就改变了value[]的内容和大小了.

比如:new StringBuffer("abc")使得value[]={'a','b','c','',''...}(注意构造的长度是 str.length()+16).如果再将这个对象append("abc"),那么这个对象中的value[]= {'a','b','c','a','b','c',''....}.这也就是为什么大家说 StringBuffer是可变字符串的涵义了.从这一点也可以看出,StringBuffer中的value[]完全可以作为字符串的缓冲区功能.其累加性能是很不错的,在后面我们会进行比较.

总结:讨论StringStringBuffer可不可变.本质上是指对象中的value[]字符数组可不可变,而不是对象引用可不可变.

5.2 StringBufferStringBuilder的线程安全性

StringBufferStringBuilder可以算是双胞胎了,这两者的方法没有很大区别.但在线程安全性方面,StringBuffer允许多线程进行字符操作.这是因为在源代码中StringBuffer的很多方法都被关键字synchronized 修饰了,StringBuilder没有.

有多线程编程经验的程序员应该知道synchronized.这个关键字是为线程同步机制设定的.我简要阐述一下synchronized的含义:

每一个类对象都对应一把锁,当某个线程A调用类对象O中的synchronized方法M,必须获得对象O的锁才能够执行M方法,否则线程A阻塞.一旦线程A开始执行M方法,将独占对象O的锁.使得其它需要调用O对象的M方法的线程阻塞.只有线程A执行完毕,释放锁后.那些阻塞线程才有机会重新调用M方法.这就是解决线程同步问题的锁机制.

了解了synchronized的含义以后,大家可能都会有这个感觉.多线程编程中StringBufferStringBuilder要安全多了,事实确实如此.如果有多个线程需要对同一个字符串缓冲区进行操作的时候,StringBuffer应该是不二选择.

注意:是不是String也不安全呢?事实上不存在这个问题,String是不可变的.线程对于堆中指定的一个String对象只能读取,无法修改.试问:还有什么不安全的呢?

5.3 StringStringBuffer的效率问题

首先说明一点:StringBufferStringBuilder可谓双胞胎,StringBuilder1.5新引入的,其前身就是 StringBuffer.StringBuilder的效率比StringBuffer稍高,如果不考虑线程安全,StringBuilder应该是首选.另外,JVM运行程序主要的时间耗费是在创建对象和回收对象上.

我们用下面的代码运行1W次字符串的连接操作,测试String,StringBuffer所运行的时间.

示例:

//测试代码
public class RunTime{
    public static void main(String[] args){
           ● 测试代码位置1
          long beginTime=System.currentTimeMillis();
          for(int i=0;i<10000;i++){
                 ● 测试代码位置2
          }
          long endTime=System.currentTimeMillis();
          System.out.println(endTime-beginTime);
    }
}
(1)String 常量与 String 变量的 "+" 操作比较

▲测试①代码: (测试代码位置1) String str="";

(测试代码位置2) str="Heart"+"Raid";

[耗时: 0ms]

▲测试②代码 (测试代码位置1) String s1="Heart";

String s2="Raid";

String str="";

(测试代码位置2) str=s1+s2;

[耗时: 15—16ms]

结论String常量的“+连接” 稍优于 String变量的“+连接”.

原因:测试①的"Heart"+"Raid"在编译阶段就已经连接起来,形成了一个字符串常量"HeartRaid",并指向堆中的拘留字符串对象.运行时只需要将"HeartRaid"指向的拘留字符串对象地址取出1W,存放在局部变量str.这确实不需要什么时间.

测试②中局部变量s1s2存放的是两个不同的拘留字符串对象的地址.然后会通过下面三个步骤完成“+连接”:

1StringBuilder temp=new StringBuilder(s1),

2temp.append(s2);

3str=temp.toString();

我们发现,虽然在中间的时候也用到了append()方法,但是在开始和结束的时候分别创建了StringBuilderString对象.可想而知:调用1W,是不是就创建了1W次这两种对象呢?不划算.

但是,String变量的"+连接"操作比String常量的"+连接"操作使用的更加广泛. 这一点是不言而喻的.

(2)String对象的"+"连接操作与StringBuffer对象的append()累和连接操作比较.

▲测试①代码: (代码位置1) String s1="Heart";

String s="";

(代码位置2) s=s+s1;

[耗时: 4200—4500ms]

▲测试②代码 (代码位置1) String s1="Heart";

StringBuffer sb=new StringBuffer();

(代码位置2) sb.append(s1);

[耗时: 0ms(当循环100000次的时候,耗时大概16—31ms)]

结论:大量字符串累加时,StringBufferappend()效率远好于String对象的"+"连接

原因:测试① 中的s=s+s1,JVM会利用首先创建一个StringBuilder,并利用append方法完成ss1所指向的字符串对象值的合并操作,接着调用 StringBuildertoString()方法在堆中创建一个新的String对象,其值为刚才字符串的合并结果.而局部变量s指向了新创建的String对象.

因为String对象中的value[]是不能改变的,每一次合并后字符串值都需要创建一个新的String对象来存放.循环1W次自然需要创建1WString对象和1WStringBuilder对象,效率低就可想而知了.

测试②中sb.append(s1);只需要将自己的value[]数组不停的扩大来存放s1即可.循环过程中无需在堆中创建任何新的对象.效率高就不足为奇了.

(3)String对象的"+"连接操作与StringBufferStringBuffer对象的append()累和连接操作比较.

示例:

package com.wangdi.test;

import junit.framework.TestCase;
/**
* 测试字符串的加
* @author gstarwd
*/
public class TestStringAdd extends TestCase{

    public void testAddByString() {
        String aa = "gstarwd";
        for(int i = 0 ; i <10000000;i++){
        aa += "gstar";
    }
}

public void testAddbyStringBuffer() {
    StringBuffer buffer  =  new StringBuffer("gstarwd");
    for(int i = 0 ; i <10000000;i++){
        buffer.append("gstar");
    }
}

public void testAddbyStringBuilder() {
    StringBuilder builder  =  new StringBuilder("gstarwd");
    for(int i = 0 ; i <10000000;i++){
        builder.append("gstar");
    }
  }
}
运行结果:

没有结果......

因为TMD4CPU在第一个10million次循环的 String加操作上耗费了很多时间,最终没有等他结束我就终止了程序.

看看占用率:

于是我把第一个String的测试用例循环次数改小了 变成10000

不多说大家自己看运行时间:

builder最佳 单线程就用StringBuilder

多线程就用StringBuffer

要是你只是存取下数据String最常用

总结

在编译阶段就能够确定的字符串常量,完全没有必要创建StringStringBuffer对象.直接使用字符串常量的"+"连接操作效率最高.
StringBuffer
对象的append效率要高于String对象的"+"连接操作.
不停的创建对象是程序低效的一个重要原因.那么相同的字符串值能否在堆中只创建一个String对象那.显然拘留字符串能够做到这一点,除了程序中的字符 串常量会被JVM自动创建拘留字符串之外,调用Stringintern()方法也能做到这一点.当调用intern(),如果常量池中已经有了当前 String的值,那么返回这个常量指向拘留对象的地址.如果没有,则将String值加入常量池中,并创建一个新的拘留字符串对象.

参考资料:
http://hui-jing-880210.iteye.com/blog/2173186
http://leowzy.iteye.com/blog/804594




20150424


JAVA学习笔记系列

--------------------------------------------

                    联系方式

--------------------------------------------

        Weibo: ARESXIONG

        E-Mail: [email protected]

------------------------------------------------

你可能感兴趣的:(String,StringBuilder,StringBuffer)