Java中的String、StringBuffer和StringBuilder

  做为做为一个已经入了门的java程序猿,确定对Java中的String、StringBuffer和StringBuilder都略有耳闻了,尤为是String 确定是常常用的。但确定你有一点很好奇,为何java中有三个关于字符串的类?一个不够吗!先回答这个问题,黑格尔曾经说过——存在必合理,单纯一个String确实是不够的,因此要引入StringBuffer。再后来引入StringBuilder是另外一个故事了,后面会详细讲到。
  要了解为何,咱们就得先来看下这三者各自都有什么样的特色,有什么样的异同,对其知根知底以后,一切谜团都会被解开。java

String

  点开String的源码,能够发现String被定义为final类型,意味着它不能被继承,再仔细看其提供的方法,没有一个能对原始字符串作任何操做的,有几个开启了貌似是操做原字符串的,好比replaceFirst replaceAll,点进去一看,实际上是从新生成了一个新的字符串,对原始内容没有作任何修改。
  是的,从实现的角度来看,它是不可变的,全部String的变动其实都会生成一个新的字符串,比String str = "abcdefghijklmnopqrstuvwxy"; str = str + "z"; 以后新生成的a-z并不包含原来的a-y,原来的a-y已经变成垃圾了。简单归纳,只要是两个不一样的字符,确定都是两个彻底不一样不相关的对象,即使其中一个是从另外一个subString出来的,两个也没有任何关系。 若是是两个相同的字符串,状况比较复杂,多是同一份也可能不是。若是在JVM中使用G1gc,并且开启-XX:+UseStringDeduplication ,JVM会对字符串的存储作优化,因此若是你的服务中有大量相同字符串,建议开启这个参数。
  Java做为一个非纯面向对象的语言,除了提供分装对象外,也提供了一些原始类型(好比:int long double char),String的使用竟然能够像用原始类型同样不须要new,直接String str = "a"这样声明,我以为String更像是面向对象和非面向对象结合的一个产物。
  String最大的特色就是 __ 不可变__,这是它的优势,由于不可变意味着使用简单,没有线程安全的问题。 但这也是它的缺点,由于每次变动都会生成一个新的字符串,明显太浪费空间了。git

StringBuffer

  我以为StringBuffer是彻底由于String的缺点而生的。咱们平常使用String的过程当中,确定常常会用到字符串追加的状况,按String的实现,没次追加即使只是一个字符,都是生成一个彻底不一样的对象,若是此次操做很频繁不少的话会大幅提升内存的消耗,而且增长gc的压力。对于这种问题,StringBuffer是如何解决的呢?咱们直接从源码上来看。
在这里插入图片描述
  但看StringBuffer里,几乎全部的方法都会调super父类,其实它全部的实现都是在AbstractStringBuilder里的。鉴于咱们对其最长用的方法是append,因此咱们就从append入手,其实append也是StringBuffer比较核心的功能。数组

/**
     * The value is used for character storage.
     */
    char[] value;
    
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
    
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

  原来是StringBuffer父类AbstractStringBuilder有个char数组value,用来存放字符串全部的字符,StringBuffer默认初始大小是16。StringBuffer在每次append的时候,若是value的容量不够,就会申请一个容量比当前所需大一倍的字符数组,而后把旧的数据拷贝进去。这种一次性扩容一倍的方式,在咱们以前HashMap源码浅析中已经看到过了。一次性多申请内存,虽然看起来会有大段的内存空闲,但其实能够减小String append时频繁建立新字符串的问题。
  因此记住,若是你代码中对String频繁操做,千万不用用String而是选择用StringBuffer或者咱们下面要讲的StringBuilder。还有一个优化点,若是你能提早知道你字符串最大的长度,建议你在建立StringBuffer时指定其capacity,避免在append时执行ensureCapacityInternal,从而提高性能。
  对于StringBuffer还有一个点没提到,注意看它源码的全部方法,除构造函数外,全部的方法都被__synchronized__修饰,意味着它是有个线程安全的类,全部操做查询方法都会被加同步,可是若是咱们只是单线程呢,想用StringBuffer的优点,但又以为加同步太多余,太影响性能。这个时候就轮到StringBuilder上场了。安全

StringBuilder

在这里插入图片描述
  StringBuilder从类图上看和StringBuffer彻底没有任何区别,再打开它的源码,和StringBuffer同样几乎啥逻辑都没有,全是调调super父类AbstractStringBuilder,它和StringBuffer最大的区别就是全部方法没有用synchronized修复,它不是一个线程安全的类,但也意味着它没有同步,在单线程状况下性能会优于StringBuffer。多线程

总结

  看完上面内容,我以为你应该知道上面时候用String、何时用StringBuffer、何时用StringBuilder了。app

  1. 若是是常量字符串,用String。
  2. 多线程环境下常常变更的字符串用StringBuffer。
  3. 单线程常常变更的字符串用StringBuilder。

彩蛋

  咱们来看个比较底层的东西,是关于jvm对String优化的,如今有以下代码。jvm

public class StringTest {
    public static void main(String[] args) {
        String str = "abc";
        str = str + "d";
        str = str + "e";
    }
}

  咱们用javac StringTest.java编译成class文件,而后用 javap -c StringTest 生成字节码,内容以下函数

public class StringTest {
  public StringTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String abc
       2: astore_1
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: aload_1
      11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      14: ldc           #6                  // String d
      16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: astore_1
      23: return
}
➜  java git:(master) ✗ javap -c StringTest
Compiled from "StringTest.java"
public class StringTest {
  public StringTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String abc
       2: astore_1
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: aload_1
      11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      14: ldc           #6                  // String d
      16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: astore_1
      23: return
}
➜  java git:(master) ✗ javac StringTest.java
➜  java git:(master) ✗ javap -c StringTest  
Compiled from "StringTest.java"
public class StringTest {
  public StringTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String abc
       2: astore_1
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: aload_1
      11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      14: ldc           #6                  // String d
      16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: astore_1
      23: new           #3                  // class java/lang/StringBuilder
      26: dup
      27: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      30: aload_1
      31: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      34: ldc           #8                  // String e
      36: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      39: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      42: astore_1
      43: return
}

  其实能够看出,java底层实现字符串+的时候实际上是用StringBuilder的append()来实现的,若是有字符串的连续+,jvm用StringBuilder append也能够实现优化。性能

备注:源码来自JDK11优化

本文由博客一文多发平台 OpenWrite 发布!
相关文章
相关标签/搜索