欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java String、StringBuffer、StringBuilder区别

程序员文章站 2022-04-22 20:27:42
...

    String类源码分析: 

  1. String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被 final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。在早期的JVM实现版本中,被final修饰的方法会被转为内嵌调用 以提升执行效率。而从Java SE5/6开始,就渐渐摈弃这种方式了。因此在现在的Java SE版本中,不需要考虑用final去提升方法调用效率。只有在确定不想让该方法被覆盖时,才将方法设置为final。
  2. String类中char value[],int  offset,int count,int hash成员属性,可以看出String类其实是通过char数组来保存字符串的。
  3.  从String类提供的方法比如subString,concat,replace等操作都算不是在原有的字符串对象上进行操作,subString是新new一个String对象,后两个都是新new一个char 数组,也就是说进行这些操作后,最原始的字符串并没有被改变。

       特别提醒:对String对象的任何改变都不影响到原对象,相关的任何修改操作都会生成新的对象。

   String StringBuffer  StringBuilder的区别;

      在 系统里面String可以满足任何操作,为什么还要有StringBuffer和StringBuilder类,给大家看段代码我想会更清楚的表达出来。

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

    我想这段代码大家很清楚就明白是什么意识,无非就是字符串循环相加,这样理解是没有错误的,在上面我们通过String类了解过,String类的任何操作都是新new一个对象, 而不是在原有的基础上进行相加,比如:string +="hello"这句话,如果10000次,那么就要创建10000个新StringBuilder对象然后进行append操作,最后通过toString方法返回String对象。也就 是说这个循环执行完毕new出了10000个对象,试想一下,如果这些对象没有被回收,会造成多大的内存资源浪费。从上面还可以看 出:string+="hello"的操作事实上会自动被JVM优化成:

  StringBuilder str = new StringBuilder(string);

  str.append("hello");

  str.toString();

    为了验证我的推理是正确的,可以通过如下代码进行验证:

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)+"毫秒");
    }
     
}

   运行结果为:

操作java.lang.String类型使用的时间为:3194毫秒
模拟JVM优化操作的时间为:3197毫秒 

    如果系统中很多地方都这样进行拼接,这将是计算机的灾难,为了提高字符串拼接的性能,让我们看一段StringBuilder的代码:

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

     从这里可以明显看出,这段代码的for循环开始到结束只进行了new操作,创建一个StringBuilder对象,字符串拼接都是通过append操作是在原有对象的基础上进行的。因此在循环了10000次之后,这段代码所占的资源要比上面小得多。有人有说了,既然StringBuilder可以提高字符串拼接的性能,那么怎么还要有StringBuffer,让我们在来看一段代码你就会明白。

 

   

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;
}

   通过两段代码可以发现就是StringBuffer比StringBuilder多了一个synchronized多了一把锁,也就是说StringBuffer是线程安全的。

  

String StringBuffer  StringBuilder的性能;

    讲过这三个方法的区别,通过程序来区别一下这三个类的性能,到底哪个性能更好些。

   

public class test {
    private static int longCount = 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<longCount; 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<longCount; 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<longCount; 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<longCount; 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<longCount; i++){
            String s = s1+s2+s3;
        }
        long over = System.currentTimeMillis();
        System.out.println("字符串间接相加操作:"+(over-begin)+"毫秒");
    }
     
}

 

    运行结果为:   

操作java.lang.String类型使用的时间为:3149毫秒
操作java.lang.StringBuffer类型使用的时间为:3毫秒
操作java.lang.StringBuilder类型使用的时间为:2毫秒
字符串直接相加操作:1毫秒
字符串间接相加操作:8毫秒

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

  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。