zoukankan      html  css  js  c++  java
  • Java中的String、StringBuffer和StringBuilder

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

    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最大的特点就是 __ 不可变__,这是它的优点,因为不可变意味着使用简单,没有线程安全的问题。 但这也是它的缺点,因为每次变更都会生成一个新的字符串,明显太浪费空间了。

    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了。

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

    彩蛋

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

    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 发布!

  • 相关阅读:
    python list添加元素的几种方法
    Python ---- list和dict遍历
    python 之 collections
    python list 中元素的统计与排序
    pandas dataframe 读取 xlsx 文件
    Python 缓存机制与 functools.lru_cache(zz)
    pip 使用
    python 中的异常处理
    python 时间日期处理
    python read txt file
  • 原文地址:https://www.cnblogs.com/xindoo/p/13035633.html
Copyright © 2011-2022 走看看