String,StringBuffer和StringBuilder这三个好基友

虽然在项目开发中,需要经常用上String处理字符串,但一直对它的理解还停留在如何使用的阶段,所以趁着这段时间复习一下,也写下博文希望把知识记得更深刻.
  
如有不正之处,请多多谅解和指正,谢谢.

1.创建String内存空间

创建String对象基本有两种方法

  1. String 变量名 = value
  2. 通过new实例化一个String对象

我们看以下例子

public static void main(String[] args) {
    String s1 = "Hello World"; //第一种创建方法
    String s2 = "Hello World";
    String s3 = new String("Hello World"); //第二种创建方法

    //String中的 == 是比较他们是否指向同一个内存空间
    System.out.println(s1 == s2);
    //运行结果为ture
    System.out.println(s1 == s3);
    //运行结果为false
}

  从这里看出s1和s2是分配到同一个内存空间里,而s3是另外一个内存空间,这两种方法虽然得出值都是Hello World但是他们的处理是大大不同的

String,StringBuffer和StringBuilder这三个好基友_第1张图片

如上图所示

  其实当s1即将要被赋值的时候Jvm会看内存里是否存在Hello World这个值的内存空间,如果没有就直接创建该值的内存空间,将s1指向到这个内存空间当中

  然后s2要被赋值时Jvm已经查到存储Hello World这个值的内存空间,所以也就直接将s2指向到这个内存空间当中

  s3比较特别因为是它是通过new出来的一个实例,Jvm不会管内存里面是否已经存在Hello World这个内存空间,而是重新开辟一个空间让s3指向过去

所以最终得出这个结果.

2.String是一个不可变类

当学习一个类最好的方法就是查看该类源码

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {

    private final char value[];

    private int hash; // Default to 0

    private static final long serialVersionUID = -6849794470754667710L;

    private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];

    public String() {
        this.value = new char[0];
    }

    ......

}

  从中我们可以看到String不管是类还是属性都被定义成final,所以每当我们对String对象进行从新赋值时,实际上就是改变内存空间的指向,而不是在原内存空间进行值的改变

很简单的道理,之前例子s1 和 s2 他们指向也是指向同一个内存空间,我改变了s2的值,因为s2指向的内存空间发生了变化,s1也会随这个变化而变化,最终导致我只想该s2结果也把s1的值也一起改了

这就是把String定义为final的原因

3.String,StringBuffer,StringBuilder的区别

因为String是不可变类,无法对字符串进行直接的修改
当做大量的字符串处理时通过String会消耗很多资源,一般我们做大量字符处理时,都会用上StringBuffer,StringBuilder这两个类

public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence {

    ......

    public synchronized StringBuffer append(String str) {
        super.append(str);
        return this;
    }
}

public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence {

    ......

    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
}

从源码中我们可以看到,不管在继承的类还是实现的接口都是一样的,唯一的不同是StringBuffer的append方法是带有synchronized,也就是说StringBuffer是线程安全的,而StringBuilder是线程非安全,因为String定义是final的关系,所以String天生就是线程安全的.

我们看下他们的执行效率

package com.rwj.test;

public class StringDemo {

    public static void main(String[] args) {
        string();
        stringBuffer();
        stringBuilder();
    }

    private static void string(){
        long startTime = System.currentTimeMillis();
        String s = new String();
        for(int i = 0; i <= 100000; i++){
            s += i;
        }
        long endTime = System.currentTimeMillis();
        System.out.println("String: " + (endTime - startTime) + "毫秒.");
    }

    private static void stringBuffer(){
        long startTime = System.currentTimeMillis();
        //线程安全
        StringBuffer s = new StringBuffer();
        for(int i = 0; i <= 100000; i++){
            s.append(i);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("StringBuffer: " + (endTime - startTime) + "毫秒.");
    }

    private static void stringBuilder(){
        long startTime = System.currentTimeMillis();
        //非线程安全
        StringBuilder s = new StringBuilder();
        for(int i = 0; i <= 100000; i++){
            s.append(i);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("StringBuilder: " + (endTime - startTime) + "毫秒.");
    }

}

运行结果为

String,StringBuffer和StringBuilder这三个好基友_第2张图片

所以需要做到大量字符时

  1. 在不考虑线程的情况下优先StringBuilder,如果加大循环次数效果尤其明显
  2. 考虑线程的情况则使用StringBuffer但运行效率也是降低

你可能感兴趣的:(Java,java,string)