JDK1.8 源码 String当真不可变么? StringBuilder和StringBuffer的区别

前言

在前面的章节内, 我们自己研究了java.lang.String类的源码. 本章主要解决与String相关的几个问题:

  • String 当真不可变么?
  • StringBuilder和StringBuffer的区别?
  • 常量池
  • +运算符的重载及其含义

String当真不可变么?

我们回顾下String的源码.

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

可以看到String真正存储数据的地方在与char value[]. 存储在一个数组内, 且其定义为final类型. 也就是这个引用是不可改变的.

且其声明为private类型的. 在外界无法访问. 但是, 如果通过反射机制还是可以更改char []数组内的值的.


import java.lang.reflect.Field;

/**
 * 使用反射更改数组内的值.
 * 
 * */
public class UpdateStringWithReflect {

	public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
		// 声明字符串
		String tmpStr = "abcde";
		System.out.println("Before tmpStr: "+tmpStr);
		
		// 获取char value[]
		Field valueFieldOfString = String.class.getDeclaredField("value");
		
		// 更改访问权限
		valueFieldOfString.setAccessible(true);
		
		// 访问对象
		char[] tmpValue = (char[]) valueFieldOfString.get(tmpStr);
		
		// 更改对象内的值 更改为hello
		tmpValue[0]='h';
		tmpValue[1]='e';
		tmpValue[2]='l';
		tmpValue[3]='l';
		tmpValue[4]='o';
		
		System.out.println("After tmp: "+tmpStr);
	
	}
}

输出结果:

Before tmpStr: abcde
After tmp: hello

可以看到其值发生了更改. 但是值得注意的是, 被final修饰的对象, 其引用是无法更改的.


StringBuilder 与 StringBuffer

StringBuilder的声明如下所示:

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

其继承的AbstractStringBuilder也有一个char[] value. 用于存储数据. 但是其无final关键字修饰. 也就是其是可变的.

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;

其中主要的方法即append()insert()方法.
在这里插入图片描述在这里插入图片描述

  • append() 方法
//  StringBuilder
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
    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;
    }
// String类中的 getChars方法

   public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }
  • 扩容流程-如何指定新数组长度?
    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;
    }
     private int hugeCapacity(int minCapacity) {
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }    

可以看到每次真实扩容的代码为int newCapacity = (value.length << 1) + 2;. 每次扩容2倍+2. 且更改value数组的指针, 因为是非final关键字修饰的.

  • insert方法
// StringBuilder
   public AbstractStringBuilder insert(int offset, String str) {
        if ((offset < 0) || (offset > length()))
            throw new StringIndexOutOfBoundsException(offset);
        if (str == null)
            str = "null";
        int len = str.length();
        // 扩充长度
        ensureCapacityInternal(count + len);
        // 将原来 offset->end 的部分进行后移动 offset->offset+str.leng 移动到 offset+str.length ->  end+str.length 即拷贝(end-offset)个char
        System.arraycopy(value, offset, value, offset + len, count - offset);
        str.getChars(value, offset);
        count += len;
        return this;
    }
// String 
// 将value值拷贝到 dst[] 数组内. 从dstBegin开始进行拷贝.
    void getChars(char dst[], int dstBegin) {
        System.arraycopy(value, 0, dst, dstBegin, value.length);
    }

由上可知, StringBuilder主要维护2个方法:

  • append方法. 末尾进行插入.
  • insert方法. 从中间的某个地方进行插入.

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

    /**
     * A cache of the last value returned by toString. Cleared
     * whenever the StringBuffer is modified.
     */
    private transient char[] toStringCache;

可以看到, 其多维护了一个对象toStringCache. 这个是干什么用的呢? 我们后面细说.

可以看到其与StringBuilder一样. 都是继承AbstractStringBuilder. 主要方法也都是appendinsert.

随后, 我们看下其append方法.

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

append方法是synchronized关键字修饰的. 也就是线程安全的.

    public AbstractStringBuilder insert(int offset, int i) {
        return insert(offset, String.valueOf(i));
    }
    public synchronized StringBuffer insert(int offset, char c) {
        toStringCache = null;
        super.insert(offset, c);
        return this;
    }

但是, 让我比较疑惑的是. 其insert方法有的是线程同步, 有的不是? 这是为什么?

  • toStringCache的作用
   public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
    }

对于toStringCache的操作. 除了在appendinsert时, 将其设置为null以外. 其余主要的操作就是在toString()方法内将其进行赋值.


Q2 结论

主要内容
  • StringBuilder与StringBuffer 都是维护可变字符串.
  • 其都是一种建造者模式Builder 模式.
  • 最主要的方法为appendinsert.
区别
  • 区别在于StringBuilder是线程不安全的.StringBuffer是线程安全的, 保证线程安全的策略是使用synchronized关键字, 对所有append方法和部分insert方法添加.

Q3 & Q4

to be continue…


Reference

[1]. 【JDK】:java.lang.String、StringBuilder、StringBuffer 源码解析
[2]. String类真的不可变吗?

©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页