【Java】String,StringBuilder和StringBuffer内存分析

继承关系

StringBuffer和StringBuilder都继承自AbstractStringBuilder这个类,而AbstractStringBuilder和String都继承自Object这个类。

String,StringBuffer, StringBuilder的区别

  • String是不可变类,而StringBuffer, StringBuilder是可变类,String中的value[]字符数组是private的,不可以修改

  • 只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。

  • 在单线程程序下,StringBuilder效率更快,因为它不需要加锁,不具备多线程安全,而StringBuffer则每次都需要判断锁,效率相对更低

StringBuffer初始化及扩容机制

  • StringBuffer()的初始容量可以容纳16个字符,当该对象的实体存放的字符的长度大于16时,实体容量就自动增加。StringBuffer对象可以通过length()方法获取实体中存放的字符序列长度,通过capacity()方法来获取当前实体的实际容量。
  • StringBuffer(int size)可以指定分配给该对象的实体的初始容量参数为参数size指定的字符个数。当该对象的实体存放的字符序列的长度大于size个字符时,实体的容量就自动的增加。以便存放所增加的字符。
  • StringBuffer(String s)可以指定给对象的实体的初始容量为参数字符串s的长度额外再加16个字符。当该对象的实体存放的字符序列长度大于size个字符时,实体的容量自动的增加,以便存放所增加的字符。

扩容算法

使用append()方法在字符串后面追加东西的时候,如果长度超过了该字符串存储空间大小了就需要进行扩容:构建新的存储空间更大的字符串,将旧的复制过去;再进行字符串append添加的时候,会先计算添加后字符串大小,传入一个方法:ensureCapacityInternal 这个方法进行是否扩容的判断,需要扩容就调用expandCapacity方法进行扩容

尝试将新容量扩为大小变成2倍+2   判断一下 容量如果不够,直接扩充到需要的容量大小

1.使用append算法

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

2.父类append


public AbstractStringBuilder append(String str) {
        if (str == null) str = "null";
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

3.ensureCapacityInternal()

private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code:判断是否需要扩容
        if (minimumCapacity - value.length > 0) 
            expandCapacity(minimumCapacity);
    }

4.expandCapacity

void expandCapacity(int minimumCapacity) { 
	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);
    }

5.Arrays.copyOf(value, newCapacity)

//copyOf方法用于创建一个新数组,新数组的长度是扩容后的长度,并将原来的值复制到新的数组
public static char[] copyOf(char[] original, int newLength) {
        char[] copy = new char[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

6.void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)

//把新字符串拷贝到原字符串便宜dstBegin处,也就是拼接
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);
    }

字符串连接可能造成的内存负担

String a = new String("test");
for(int i = 0; i<1000;i++)
{
    a+=i;
}

在用a+=i连接字符串时候,每连接一次就会在堆内新分配一个内存空间新建一个stringbuffer对象来连接,如果单线程的话就要创建1002个对象(“test”一个对象,string a一个对象,1000个连接字符串时候产生的对象),在单线程情况下可能看不出影响,但如果在服务器端运行,如果很多请求,对应着很多线程就会造成创建很多对象。

解决方法:尽量使用stringbuffer中的string.append()来连接字符串。

String a = "a"+"b"+"c";

这种情况下用string,系统会自动用stringbuffer来处理,效率高

String a = "a";
string b = "b";
String c = "c";
String res = a+b+c;

StringBuffer res = new StringBuffer(a).append(b).append(c);

这种情况下,stringbuffer会更节省内存。

猜你喜欢

转载自blog.csdn.net/weixin_38516944/article/details/81200085