Java基础-String、StringBuffer、StringBuilder

看下面这段代码:

public class Main {

    public static void main(String[] args) {
        String string = "";
        for(int i=0;i<10000;i++){
            string += "hello";
        }
    }
}

这句 string += "hello";的过程相当于将原有的string变量指向的对象内容取出与"hello"作字符串相加操作再存进另一个新的String对象当中,再让string变量指向新生成的对象。

从这段反编译出的字节码文件可以很清楚地看出:从第8行开始到第35行是整个循环的执行过程,并且每次循环会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象。也就是说这个循环执行完毕new出了10000个对象,试想一下,如果这些对象没有被回收,会造成多大的内存资源浪费。从上面还可以看出:string+="hello"的操作事实上会自动被JVM优化成:

StringBuilder str = new StringBuilder(string);
str.append("hello");
str.toString();

再看下面这段代码:

public class Main {

    public static void main(String[] args) {
        StringBuilder stringBuilder = new StringBuilder();
        for(int i=0;i<10000;i++){
            stringBuilder.append("hello");
        }
    }
}

反编译字节码文件得到:

从这里可以明显看出,这段代码的for循环式从13行开始到27行结束,并且new操作只进行了一次,也就是说只生成了一个对象,append操作是在原有对象的基础上进行的。因此在循环了10000次之后,这段代码所占的资源要比上面小得多。

那么有人会问既然有了StringBuilder类,为什么还需要StringBuffer类?查看源代码便一目了然,事实上,StringBuilder和StringBuffer类拥有的成员属性以及成员方法基本相同,区别是StringBuffer类的成员方法前面多了一个关键字:synchronized,不用多说,这个关键字是在多线程访问时起到安全保护作用的,也就是说StringBuffer是线程安全的。

下面摘了2段代码分别来自StringBuffer和StringBuilder,insert方法的具体实现:

StringBuilder的insert方法

public StringBuilder insert(int index, char str[], int offset,
                              int len)
  {
      super.insert(index, str, offset, len);
    return this;
  }

StringBuffer的insert方法:

public synchronized StringBuffer insert(int index, char str[], int offset,
                                            int len)
    {
        super.insert(index, str, offset, len);
        return this;
    }

性能测试

public class Main {
    private static int time = 50000;
    public static void main(String[] args) {
        testString();
        testStringBuffer();
        testStringBuilder();
        test1String();
        test2String();
    }

    public static void testString () {
        String s="";
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
            s += "java";
        }
        long over = System.currentTimeMillis();
        System.out.println("操作"+s.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
    }

    public static void testStringBuffer () {
        StringBuffer sb = new StringBuffer();
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
            sb.append("java");
        }
        long over = System.currentTimeMillis();
        System.out.println("操作"+sb.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
    }

    public static void testStringBuilder () {
        StringBuilder sb = new StringBuilder();
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
            sb.append("java");
        }
        long over = System.currentTimeMillis();
        System.out.println("操作"+sb.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
    }

    public static void test1String () {
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
            String s = "I"+"love"+"java";
        }
        long over = System.currentTimeMillis();
        System.out.println("字符串直接相加操作:"+(over-begin)+"毫秒");
    }

    public static void test2String () {
        String s1 ="I";
        String s2 = "love";
        String s3 = "java";
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
            String s = s1+s2+s3;
        }
        long over = System.currentTimeMillis();
        System.out.println("字符串间接相加操作:"+(over-begin)+"毫秒");
    }

}

结果

上面提到string+="hello"的操作事实上会自动被JVM优化,看下面这段代码:

public class Main {
    private static int time = 50000;
    public static void main(String[] args) {
        testString();
        testOptimalString();
    }

    public static void testString () {
        String s="";
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
            s += "java";
        }
        long over = System.currentTimeMillis();
        System.out.println("操作"+s.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
    }

    public static void testOptimalString () {
        String s="";
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
            StringBuilder sb = new StringBuilder(s);
            sb.append("java");
            s=sb.toString();
        }
        long over = System.currentTimeMillis();
        System.out.println("模拟JVM优化操作的时间为:"+(over-begin)+"毫秒");
    }

}

得到验证。

  下面对上面的执行结果进行一般性的解释:

1)对于直接相加字符串,效率很高,因为在编译器便确定了它的值,也就是说形如"I"+"love"+"java"; 的字符串相加,在编译期间便被优化成了"Ilovejava"。这个可以用javap -c命令反编译生成的class文件进行验证。

  对于间接相加(即包含字符串引用),形如s1+s2+s3; 效率要比直接相加低,因为在编译器不会对引用变量进行优化。

2)String、StringBuilder、StringBuffer三者的执行效率:  

StringBuilder > StringBuffer > String

当然这个是相对的,不一定在所有情况下都是这样。

比如String str = "hello"+ "world"的效率就比 StringBuilder st  = new StringBuilder().append("hello").append("world")要高。

  因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用:

  当字符串相加操作或者改动较少的情况下,建议使用 String str="hello"这种形式;

  当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。

关于String、StringBuffer的面试题

1. 下面这段代码的输出结果是什么?

String a = "hello2";   
String b = "hello" + 2;   
System.out.println((a == b));

  输出结果为:true。原因很简单,"hello"+2在编译期间就已经被优化成"hello2",因此在运行期间,变量a和变量b指向的是同一个对象。

2.下面这段代码的输出结果是什么?  

String a = "hello2";  
String b = "hello";
String c = b + 2;
System.out.println((a == c));

  输出结果为:false。由于有符号引用的存在,所以  String c = b + 2;不会在编译期间被优化,不会把b+2当做字面常量来处理的,因此这种方式生成的对象事实上是保存在堆上的。因此a和c指向的并不是同一个对象。javap -c得到的内容:

3.下面这段代码的输出结果是什么?

String a = "hello2";    
final String b = "hello";
String c = b + 2;
System.out.println((a == c));

输出结果为:true。对于被final修饰的变量,会在class文件常量池中保存一个副本,也就是说不会通过连接而进行访问,对final变量的访问在编译期间都会直接被替代为真实的值。那么String c = b + 2;在编译期间就会被优化成:String c = "hello" + 2;

4.下面这段代码输出结果为:

public class Main {
    public static void main(String[] args) {
        String a = "hello2";
        final String b = getHello();
        String c = b + 2;
        System.out.println((a == c));
    }

    public static String getHello() {
        return "hello";
    }
}

输出结果为false。这里面虽然将b用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定,因此a和c指向的不是同一个对象。

5.下面这段代码的输出结果是什么?

public class Main {
    public static void main(String[] args) {
        String a = "hello";
        String b =  new String("hello");
        String c =  new String("hello");
        String d = b.intern();

        System.out.println(a==b);
        System.out.println(b==c);
        System.out.println(b==d);
        System.out.println(a==d);
    }
}

这里面涉及到的是String.intern方法的使用。在String类中,intern方法是一个本地方法,在JAVA SE6之前,intern方法会在运行时常量池中查找是否存在内容相同的字符串,如果存在则返回指向该字符串的引用,如果不存在,则会将该字符串入池,并返回一个指向该字符串的引用。因此,a和d指向的是同一个对象。

6.String str = new String("abc")创建了多少个对象

这个问题自身就没有合理的答案,大部分网上流传的以及一些面试书籍上都说是2个对象(一个是“xyz”,一个是指向“xyz”的引用对象s),这种说法是片面的。

这问题的毛病是什么呢?它并没有定义“创建了”的意义。 
什么叫“创建了”?什么时候创建了什么? 
而且这段Java代码片段实际运行的时候真的会“创建两个String实例”么?

如果这道是面试题,那么可以当面让面试官澄清“创建了”的定义,然后再对应的回答。这种时候面试官多半会让被面试者自己解释,那就好办了,好好晒给面试官看。

如果是笔试题就没有提问要求澄清的机会了。不过会出这种题目的地方水平多半也不怎么样。说不定出题的人就是从各种宝典上把题抄来的,那就按照宝典把那不大对劲的答案写上去就能混过去了

先换成另一个问题来问:

String s = new String("xyz");  在运行时涉及几个String实例?

一种合理的解答是:

两个,一个是字符串字面量"xyz"所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,另一个是通过new String(String)创建并初始化的、内容与"xyz"相同的实例

也就是规定了Java语言一般是编译为Java虚拟机规范所定义的Class文件,但并没有规定“一定”(must),留有不使用JVM来实现Java语言的余地。 
考虑上Java虚拟机规范,确实在这段代码里涉及的常量种类为CONSTANT_String_info的字符串常量也只有"xyz"一个。CONSTANT_String_info是用来表示Java语言中String类型的常量表达式的值(包括字符串字面量)用的常量种类,只在这个层面上考虑的话,这个解答也没问题。 
所以这种解答可以认为是合理的。

值得注意的是问题中“在运行时”既包括类加载阶段,也包括这个代码片段自身执行的时候。

String s = new String("xyz");  涉及用户声明的几个String类型的变量?

一个,就是String s。

String s = null;  涉及用户声明的几个String类型的变量?

Java里变量就是变量,引用类型的变量只是对某个对象实例或者null的引用,不是实例本身。声明变量的个数跟创建实例的个数没有必然关系,像是说: 
String s1 = "a";
String s2 = s1.concat("");
String s3 = null;
new String(s1);  

这段代码会涉及3个String类型的变量, 
1、s1,指向下面String实例的1 
2、s2,指向与s1相同 
3、s3,值为null,不指向任何实例

以及3个String实例, 
1、"a"字面量对应的驻留的字符串常量的String实例 
2、""字面量对应的驻留的字符串常量的String实例 
String.concat()是个有趣的方法,当发现传入的参数是空字符串时会返回this,所以这里不会额外创建新的String实例) 
3、通过new String(String)创建的新String实例;没有任何变量指向它。

....具体讨论查阅http://rednaxelafx.iteye.com/blog/774673/

7.下面这段代码1)和2)的区别是什么?

public class Main {
    public static void main(String[] args) {
        String str1 = "I";
        //str1 += "love"+"java";        1)
        str1 = str1+"love"+"java";      //2)

    }
}

1)的效率比2)的效率要高,1)中的"love"+"java"在编译期间会被优化成"lovejava",而2)中的不会被优化。

时间: 2024-10-17 22:23:45

Java基础-String、StringBuffer、StringBuilder的相关文章

Java的String&amp;StringBuffer&amp;StringBuilder

一:String类 1.String对象的初始化 由于String对象特别用,所以在对String对象进行初始化时,Java提供了一种简化的特殊语法,格式如下: String s = "abc"; s = "Java语言"; 其实按照面向对象的标准语法,其格式应该为: String s = new String("abc"); s =new String("Java语言"); 只是按照面向对象的标准语法,在内存使用上存在比较大

java中String&amp;StringBuffer&amp;StringBuilder

1. String,字符串常量 StringBuffer,字符串变量(线程安全) StringBuilder,字符串变量(非线程安全) 2.String and StringBuffer String和StringBuffe的主要区别在于String是不可变对象,每次对String进行改变的时候其实 等于生成了一个新的String对象,因此经常改变内容的字符串不要使用String类型,因为每次形成新的对象都会对系统的性能产生影响,特别当内存中的无引用的对象多了之后,JVM的GC就会开始工作,那速

黑马程序员——Java基础---String与StringBuilder

String类    一.特点 String是字符串的类类型,用于描述字符串事物.字符串是一个特殊的对象并且因为String对象特别常用,所以在对String对象进行初始化时,Java提供了一种简化的特殊语法,例如:String s=”123”.以及String s=123+””; 字符串最大的特点就是:一旦被初始化就不可以被改变. 例: String s1=“abc”: String s2 = new String(“abc”): String s3=“abc”: 比较的结果: s1==s2

Java之String,StringBuffer,StringBuilder类

在 java 语言中, 用来处理字符串的的类常用的有 3 个: String.StringBuffer.StringBuilder. 它们的异同点: 1) 都是 final 类, 都不允许被继承; 2) String 长度是不可变的, StringBuffer.StringBuilder 长度是可变的; 3) StringBuffer 是线程安全的, StringBuilder 不是线程安全的.就是说StringBuffer类中的很多方法使用了synchronized关键字修饰,允许多线程进行操

java中string , StringBuffer , StringBuilder 区别

1.String String变量的值不能改变,如果要改变String变量的值,虚拟机首先会遍历方法区中的字符串常量,如果存在需要的值,则虚拟机直接把此常量值的地址分配给String变量,如果不存在这样的值,虚拟机则会另外在方法区中划分出一块内存空间存储字符串常量,然后把这块地址赋给String变量. 因此,String类的内容声明后不可被改变,改变的只是其内存地址的指向. 2.StringBuffer StringBuffer是使用缓冲区存储字符串对象的,对象的内容可以改变.并且是线程安全的.

整理Java基础知识--StringBuffer&amp;StringBuilder类

StringBuffer 类 & StringBuilder 类String 长度大小不可变StringBuffer 和 StringBuilder 长度可变StringBuffer 线程安全 StringBuilder 线程不安全StringBuilder 速度快StringBuffer 类 & StringBuilder 类的对象能够被多次修改,并且不产生新的未使用对象(String会产生新的,并且比较耗费内存)StringBuffer 方法1.指定字符串追加字符到这个字符序列xxxx

Java中String, StringBuffer, StringBuilder

String: 1,是字符串常量,一旦创建就不能修改.对于已经存在了的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去. 2,String也是final类,不能被继承. 3,而且String是对象而不是基本类型. 4,string重写了equals方法和hashCode()方法. StingBuffer: 1,是字符串可变对象,可以对字符串进行操作,修改字符串原有值时不会新建一个对象. 2,执行效率较慢,但是线程安全 3,StringBuffer没有覆盖equals方法和ha

关于Java基础知识 String StringBuffer StringBuilder三者的区别

Java基础中String StringBuffer StringBuilder 以下介绍 相同点:String,StringBuffer,StringBuilder最终底层存储与操作的都是char数组,StringBuffer和StringBuilder都继承了AbstractStringBuilder 不同点:String:char数组是final的,不可变,修改String时实际上是new一个新String对象返回,线程安全,频繁的增删操作时不建议使用 StringBuffer:线程安全(

探秘Java中String、StringBuilder以及StringBuffer

探秘Java中String.StringBuilder以及StringBuffer 相信String这个类是Java中使用得最频繁的类之一,并且又是各大公司面试喜欢问到的地方,今天就来和大家一起学习一下String.StringBuilder和StringBuffer这几个类,分析它们的异同点以及了解各个类适用的场景.下面是本文的目录大纲: 一.你了解String类吗? 二.深入理解String.StringBuffer.StringBuilder 三.不同场景下三个类的性能测试 四.常见的关于

【Java】String,StringBuffer与StringBuilder的区别??

String 字符串常量StringBuffer 字符串变量(线程安全)StringBuilder 字符串变量(非线程安全) 简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以