第五章 Java的基础类之String、StringBuffer、StringBuilder(下)

前言

在现实世界中,既有独居动物,也有群居动物。人作为一种高等动物,是一种群居动物。群居动物都离不开家族,String类上篇中已经详细分析了它的源码实现,几乎涵盖了大部分的源码。作为一个优秀的类,它是怎样产生的呢?它的家族、派系是怎样的?为了以后编码过程中更加清晰明白,不被混淆,这篇文章来对比分析一下String、StringBufffer、StringBuilder、AbstractStringBuilder 以及 CharSequence接口。
String的源码解析请看第四章:《第四章 JAVA的基础类之String源码分析(上)》
第四章 JAVA的基础类之String源码分析(上)

家族结构

上古传说:开天辟地有一虫,虫大成蛇,蛇大成蟒,蟒大成蚺,蚺大成蛟,蛟大成龙…成龙的儿子…扯远了。
String是怎么来的呢?
在这里插入图片描述
从图中可以看到,在1.8版本中,String 的父类有CharSequence 和 AbstractStringBuilder 。Sting的兄弟类有StringBuffer 和 StringBuilder。那么谁是老大、谁是老二、谁是老三呢?

接口 CharSequnce

这是一个字符序列的接口,它定义了一个字符串应该具备的行为特征。其中方法如下:

int length();
char charAt(int index);
CharSequence subSequence(int start, int end);
String toString();

AbstractStringBuilder

 这是一个抽象类,诞生于 JAVA 1.5,和StringBuilder 一起诞生出来的。它是对StringBuffer 和StringBuilder 
 的抽象。

StringBuffer 和 StringBuilder

这两个类,也是final修饰过的,被final修饰过的类不能被继承,方法不能被重写。所以这个类提供的行为方法就是最基本的方法了。

(1)不同点

安全性比较
StringBuffer的所有方法,都是被synchronized修饰过的,因此它是线程安全的,但是速度也会受到影响而降低。

性能比较
StringBuilder的所有方法,没有被synchronized修饰,相比较StringBuffer,它是线程不安全的,但是单线程下,速度比StringBuffer快。其原因可能是StringBuffer的修饰词synchronized带来的消耗。

属性比较
它们都是继承的AbstractStringBuilder,其内部维护着一个数组 value 和 字符串长度 count;
count 与 value.length 它们是不同的。最开始的时候我也产生过这样的疑惑,那是在看ArrayList源码的时候,后来才反应过来。value.length 是指的容器的最大容量,count 是指的实际的容量。
实际容量 <= 最大容量;
但是StringBuffer内部单独维持着一个数组,StringBuilder没有。它是被transient修饰的属性,表示不会被序列化。

/**
 * A cache of the last value returned by toString. Cleared
 * whenever the StringBuffer is modified.
 * 翻译: toString方法返回的最后一个值的缓存。在修改StringBuffer时清除
 */
private transient char[] toStringCache;

(2)相同点

构造器
两者的构造器使用无参构造函数时,其内部维持的数组长度默认为16.
如果使用有参构造时,其内部容量为16+参数的长度。

方法
其内部基本上都提供了相同的方法,方法都来自于父类,两者对其中的部分方法进行了重写,其区别就是StringBuffer加了synchronized修饰词,StringBuilder没有加。两者总结出来大概有【增】、【删】、【查】、【改】四大部分。并且其内部实现都是调用的父类的方法。

//  增: 主要是append方法和insert方法
	append(CharSequence s)
	append(CharSequence s, int start, int end)
	append(char[] str)
	append(char[] str, int offset, int len)
	append(char c)
	append(boolean b)
	append(int i)
	append(long lng)
	append(float f)
	append(double d)
	insert(int index, char[] str, int offset,int len)
	insert(int offset, Object obj)
	insert(int offset, String str)
	insert(int offset, char[] str)
	insert(int dstOffset, CharSequence s)
	insert(int dstOffset, CharSequence s,int start, int end)
//  删:
	delete(int start, int end)
	deleteCharAt(int index)
//  改:
	replace(int start, int end, String str)
//  查:
	indexOf(String str)
	indexOf(String str, int fromIndex)
	lastIndexOf(String str)
	lastIndexOf(String str, int fromIndex)
	toString()

这些方法的作用和String类中方法的作用区别不大,但是为什么已经有了String类这个优秀的儿子了,为什么还要诞生出它的兄弟StringBuffer 和 StringBuilder 呢?

StringBuffe诞生原因分析

从源码中可以得知,String诞生于JAVA 1.0,StringBuffer 也诞生于 JAVA 1.0。而 StringBuilder 诞生于 JAVA 1.5。
先来分析String 和 StringBuffer,这对双胞胎。
String 和 StringBuffer 都是final 修饰的,不可被继承。但是有一点不同,String 内部维护的数组也是被fina修饰的,而StringBuffer 内部维护的数组,没有被final修饰,是可变的。
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;

String 类虽然不是基本数据类型,但是它非常强大,内部有着丰富的API,使用程度非常高。但是这个类有一个缺点,由于其内部的数组是不可变的,所以String 类中涉及到的【增】、【删】、【改】操作时,凡是和char容量变更相关的操作,它只能重新创建一个String 类,意味着有一个新的容量的数组。这样的变更操作会产生大量的对象在堆中产生,非常影响性能!
StringBuffer 类

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

这个类继承了AbstractStringBuilder类,AbstractStringBuilder是JAVA 1.5才出现的,和StringBuilder 一致。
StringBuffer 中的属性被抽象到了 AbstractStringBuilder类中,AbstractStringBuilder属性中维护着一个数组char[ ] value:

abstract class AbstractStringBuilder implements Appendable, CharSequence {

/**
 * The value is used for character storage.
 */
char[] value;

/**
 * The count is the number of characters used.
 */
int count;

StringBuffer 继承了这个类,因此StringBuffer 中的这个数组是容量可变的。并且StringBuffer 中提供的API
大部分属于【增】、【删】、【改】。
因此,StringBuffer 很好的弥补了String类对于内部数组容量变更操作的缺点,它们相互协作可以提供功能方便、性能强大的API组合。具体是在哪些地方做了优化呢?下面通过StringBuffer的append方法来研究一下。

实例分析

 // 下面左侧是行序
 public static void main(String[] args) {
//1    long d ;
//2    long t = System.currentTimeMillis();
//3    StringBuffer sd = new StringBuffer();
//4   for (int i = 0; i < 10000; i++) {
//5        sd.append(i);
//6    }
//7    System.out.println((d=System.currentTimeMillis()) - t);
//8    String s = "";
//9    for (int i = 0; i < 10000; i++) {
//10                s += i;
//11      }
//12    System.out.println(System.currentTimeMillis() - d);
}
// 结果:
	1
	331

可以看到StringBuffer 在改变容量操作的过程中,性能远远高于String的自增。下面分析一下其内部原理。

(1) 构造器

在第3行创建StringBuffer 对象时,使用了无参构造,内部的char数组默认容量为16.
在第8行创建String类时,使用了无参构造,默认是空字符。

(2)扩容机制

String类的扩容机制就是重新new 一个String,创建一个新增需要的长度的char 数组。每一次循环,都会做这样的操作。也就是每一次都要new 新的String对象,并且底层复制新的数组出来。
StringBuffer 初始容量为16,
也就是char [ ] value = new char[16];
前16次循环只需要赋值到value中就可以了。超过了16呢?
StringBuffer 有这样的扩容机制:
在append() 方法中,其实现是调用的父类的方法。所以观察一下父类 AbstractStringBuilder:

	// append 方法
	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;
}

// ensureCapacityInternal 方法,这个是扩容
private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}

//newCapacity方法  ,  计算新容量大小
private int newCapacity(int minCapacity) {   	// minCapacity 是所需容量
    // overflow-conscious code
    int newCapacity = (value.length << 1) + 2;  // 设置新容量为原容量的 2倍+2
    if (newCapacity - minCapacity < 0) {    //  如果新容量仍然小于 所需的容量
        newCapacity = minCapacity;   		// 则新容量为所需容量,否则新容量就为原来的 2倍 + 2
    }
    return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
        ? hugeCapacity(minCapacity)
        : newCapacity;
}

//hugeCapacity方法,针对计算容量过程中,计算出的容量不在 0 到 MAX_ARRAY_SIZE范围内时的解决方案:
private int hugeCapacity(int minCapacity) {
    if (Integer.MAX_VALUE - minCapacity < 0) {  // overflow    
        throw new OutOfMemoryError();   		// 如果 计算出的容量超过了Int 类型的最大范围即2^31,则报错
    }
    return (minCapacity > MAX_ARRAY_SIZE)   	// 否则就返回minCapacity 和 MAX_ARRAY_SIZE之间的最大值
        ? minCapacity : MAX_ARRAY_SIZE;
}

因此,正因为StringBuffer 有了这个扩容机制,所以在自增的时候,不需要新建一个对象,在性能上有了很大的提升。
这是String 和 StringBuffer 之间的区别以及StringBuffer 诞生的原因。

StringBuilder的诞生原因

StringBuilder 是 JAVA 1.5才出来的,相比于StringBuffer ,StringBuilder 在StringBuffer的基础上,提供的是线程不安全的API,减少了synchronized的使用,它们两者API都被抽象到了AbstractStringBuilder类中,其实现也是在父类中,所以两者除了这个区别以外,没有其他区别。
但是synchronized带来的区别有其二:
一是StringBuffer用synchronized修饰的方法是属于线程安全的,StringBuilder 是线程不安全的。
二是synchronized修饰的API速度比没有修饰的相同API更慢,其原因暂不清楚,但是可以推测应该是synchronized带来的性能消耗。具体待以后演技JVM时,再来做确定的答案。

总结:

回到开头的问题,String和StringBuffer 是双胞胎,StringBuilder 是老三。

猜你喜欢

转载自blog.csdn.net/weixin_43901067/article/details/104718429