Java基础 - String、StringBuffer、StringBuilder有什么区别?

291 阅读2分钟

String类

String类用了final修饰,是不可变的类,所有的属性也是不可变的。

  • 直接赋值: 在通过直接赋值的方式创建String对象时(String str = "123";),虚拟机会去检查字符串常量池中是否存在相同的字符串(通过equals方法),存在则返回池中对象引用;不存在则新建一个字符串对象放入池中,并返回对象引用。所以在使用字符串拼接的时候,会产生大量无用的中间对象,这个过程新建对象、回收对象会花费大量时间和空间,是不太可取的。
    • JDK1.8中字符串常量池(包括运行时常量池)在物理层面是存在于内存的堆中的,而逻辑上时属于方法区,这里是容易造成误解的地方。
  • new对象: 如果通过new方法赋值的话,虚拟机不会去检查字符串常量池,而是直接在堆区或者栈上创建一个新的对象。
  • 缓存: String在Java6以后提供了intern()方法,目的是为了将字符串缓存起来,已被重复使用,但是在6版本中,被缓存的字符串存放在“永久代”中,这里空间有限,使用不当会造成内存溢出(OOM);在后续的版本中就被放到了堆中,因为intern需要显式的调用,很麻烦,其实也很难保证效率,所有用的少。

StringBuffer、StringBuilder

所以为了实现对字符串的各种操作,就有了StringBuffer和StringBuilder。

  • 线程安全: 它们都继承了AbstractBuilder,所以基本的操作方法都是一样的,区别在于StringBuffer所有的方法都加了sychronized修饰,所以StringBuffer是线程安全的,StringBuilder是非线程安全的。
  • 底层实现: 在JDK1.8中,它们的底层都是用了可修改的数组实现的(1.9用了byte),初始长度是16,在拼接字符的时候,如果容量不够就会创建新的数组,长度为原来的两倍再加2,然后进行数组复制。
 /**
     * This implements the expansion semantics of ensureCapacity with no
     * size check or synchronization.
     */
    void expandCapacity(int minimumCapacity) {
    // 扩容
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        // 数组复制
        value = Arrays.copyOf(value, newCapacity);
    }