Java中的String、StringBuilder以及StringBuffer

JavaString、StringBuilder以及StringBuffer

一、JavaString类

关于String类的两种创建方法:

public class test1 {
    public static void main(String[] args) {
    String s1 = "Runoob";        // String 直接创建
	String s2 = "Runoob";       // String 直接创建
	String s3 = s1;                    // 相同引用
	String s4 = new String("Runoob");// String 对象创建
	String s5 = new String("Runoob");// String 对象创建

     System.out.println(s1==s4);//false
     System.out.println(s1==s2);//true
     System.out.println(s2==s3);//true
     System.out.println(s4==s5);//false
    }

}

原因:

  • String 创建的字符串存储在公共池中,而new创建的字符串对象在堆上。
  • 由Strin创建的字符串都在编译期间生成了字面常量和符号引用,运行期间字面常量“Ruboob”被存储在公共池(当然只是保存了一份)。这种方式来将String对象跟引用绑定的话,JVM执行引擎会在运行公共池查找是否存在相同的字面常量,如果存在,则直接将引用指向已经存在的字面常量,并将引用指向该字面常量。
  • 众所周知,通过nwq关键字来生成对象是子堆区进行的,而在堆区进行对象的生成的过程是不会去检测对象是否存在的。因此通过new来创建对象,创建出的一定是不同的对象,即使字符串的内容分是相同的。

在这里插入图片描述

我们通过String源码来了解该类的实现。
打开这个类文件,我们就可以发现String类是被final修饰的。

public final class String implements Serializable, Comparable<String>, CharSequence {
    @Stable
    private final byte[] value;
    private final byte coder;
    private int hash;
    private static final long serialVersionUID = -6849794470754667710L;
    static final boolean COMPACT_STRINGS = true;
    private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
    public static final Comparator<String> CASE_INSENSITIVE_ORDER = new String.CaseInsensitiveComparator();
    static final byte LATIN1 = 0;
    static final byte UTF16 = 1;

从上面可以看出几点:

  1. String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final。在java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。在早期的JVM实现版本中,被final修饰的方法会被转为内嵌调用以提升执行效率。而从JavaSE5/6开始,就渐渐摒弃这种方式了,因此在现在的JavaSE版本中,不需要考虑final去提升方法调用效率。只有在确定不想让该方法被覆盖的时,才将方法设置为final。
  2. 上面列举出了String类中所有的成员属性,从上面可以看出String类其实是通过char数组来保存字符串的。
public  String substring( int  beginIndex,  int  endIndex) {
     if  (beginIndex <  0 ) {
         throw  new  StringIndexOutOfBoundsException(beginIndex);
     }
     if  (endIndex > count) {
         throw  new  StringIndexOutOfBoundsException(endIndex);
     }
     if  (beginIndex > endIndex) {
         throw  new  StringIndexOutOfBoundsException(endIndex - beginIndex);
     }
     return  ((beginIndex ==  0 ) && (endIndex == count)) ?  this  :
         new  String(offset + beginIndex, endIndex - beginIndex, value);
     }
 
  public  String concat(String str) {
     int  otherLen = str.length();
     if  (otherLen ==  0 ) {
         return  this ;
     }
     char  buf[] =  new  char [count + otherLen];
     getChars( 0 , count, buf,  0 );
     str.getChars( 0 , otherLen, buf, count);
     return  new  String( 0 , count + otherLen, buf);
     }
 
  public  String replace( char  oldChar,  char  newChar) {
     if  (oldChar != newChar) {
         int  len = count;
         int  i = - 1 ;
         char [] val = value;  /* avoid getfield opcode */
         int  off = offset;    /* avoid getfield opcode */
 
         while  (++i < len) {
         if  (val[off + i] == oldChar) {
             break ;
         }
         }
         if  (i < len) {
         char  buf[] =  new  char [len];
         for  ( int  j =  0  ; j < i ; j++) {
             buf[j] = val[off+j];
         }
         while  (i < len) {
             char  c = val[off + i];
             buf[i] = (c == oldChar) ? newChar : c;
             i++;
         }
         return  new  String( 0 , len, buf);
         }
     }
     return  this ;

从上面的三个方法可以看出,无论是sub操作,concat还是replace操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且大量浪费有限的内存空间。

String a="a";		//假设a的地址是0x00001;
a="b"; 				//重新赋值以后a的会指向b的地址0x00002,但是0x00001地址依旧存在。

因此String的操作都是改变赋值地址而不是改变值操作。

二、JavaStringBuffer和StringBuilder类

  • 当对字符串进行修改的时候,需要使用StringBuffer和StringBulider类。和String类不同是,StringBufffer和StringBuilder类的对象能够被多次的修改,并且不产生新的未使用对象。
    在这里插入图片描述

  • 在使用StringBuffer类时,每次都会对StringBuffer对象本身进行操作,而股市生成新的对象,所以如果需要对字符串进行修改使用StringBuffer。

  • 那么有人就会问了,既然由了StringBuilder类,为什么还需要StringBuffer类?事实上,S听Builder和StringBuilder类拥有的成员属性以及成员方法基本相同,区别是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;
  }

StringBuilder的insert方法

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

三、不同场景下三个类的性能测试

下面我们来测试以下三个类的性能区别,以及字符串直接连接和间接连接的区别。

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

}

在这里插入图片描述
事实上String+="java"的操作会自动被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”+"you"的字符串相加,在编译期间便被优化成为了“Iloveyou”。
    对于间接相加(即包含字符串引用),形如s1+s2+s3;效率要比直接相加低,因为在编译器不会对引用变量进行优化。

  2. String、StringBuilder、StringBuffer三者的执行效率
    StringBuilder>StringBuffer>String
    当然这个是在多次修改值的时候的情况下成立。
    比如像上面的那个例子。String s+="java"的效率就比StringBuilder s=new StringBuilder().append(“java”)要高。

  3. 因此根据不同的情况来进行选择使用:
    当字符串相加操作或者改动较少的情况下,建议使用String str="hello"这种形式。
    当字符串相加操作或者改动较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值