String, StringBuffer, StringBuilder

Consider below code with three concatenation functions with three different types of parameters, String, StringBuffer and StringBuilder:

// Java program to demonstrate difference between String,
// StringBuilder and StringBuffer
class Geeksforgeeks
{
    // Concatenates to String
    public static void concat1(String s1)
    {
        s1 = s1 + "forgeeks";
    }
 
    // Concatenates to StringBuilder
    public static void concat2(StringBuilder s2)
    {
        s2.append("forgeeks");
    }
 
    // Concatenates to StringBuffer
    public static void concat3(StringBuffer s3)
    {
        s3.append("forgeeks");
    }
 
    public static void main(String[] args)
    {
        String s1 = "Geeks";
        concat1(s1);  // s1 is not changed
        System.out.println("String: " + s1);
 
        StringBuilder s2 = new StringBuilder("Geeks");
        concat2(s2); // s2 is changed
        System.out.println("StringBuilder: " + s2);
 
        StringBuffer s3 = new StringBuffer("Geeks");
        concat3(s3); // s3 is changed
        System.out.println("StringBuffer: " + s3);
    }
}

Output:

String: Geeks
StringBuilder: Geeksforgeeks
StringBuffer: Geeksforgeeks

Explanation:

  1. Concat1 : In this method, we pass a string “Geeks” and perform “s1 = s1 + ”forgeeks”. The string passed from main() is not changed, this is due to the fact that String is immutable (String不可变是因为在JDK中String类被声明为一个final类). Altering the value of string creates another object and s1 in concat1() stores reference of new string. References s1 in main() and cocat1() refer to different strings.

  2. Concat2 : In this method, we pass a string “Geeks” and perform “s2.append(“forgeeks”)” which changes the actual value of the string (in main) to “Geeksforgeeks”. This is due to the simple fact that StringBuilder is mutable and hence changes its value.

  3. Concat3 : StringBuffer is similar to StringBuilder except one difference that StringBuffer is thread safe, i.e., multiple threads can use it without any issue. The thread safety brings a penalty of performance.

问题0:String为什么是不可变的?
String的不可变指的是当String变量被再次赋值时改变的是它的引用地址,而不是修改原来所引用的地址对应的数据。
它不可变的原因在于以下源码:

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

final class的定义保证它不能被继承,从而不会被破坏原有的规则。
private final char value[]中,private的定义也确保了value数组不会被轻易操作,final则只是保证了value的引用地址不变。所以实现其不可变性的重点在于String类的底层实现都没有动到这个数组里的数据,并且通过final+private的权限控制来限制访问。所以不能简单解释为通过final实现的。

问题1:StringBuffer是线程安全的,StringBuilder是线程不安全的,是使用什么方式实现的线程安全呢?

任何问题,如果想究其本质还需寻根源,所以翻阅一下JDK源码问题便可知晓:

StringBuffer的源码如下:

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

StringBuilder的源码如下:

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

对过对比即可清楚看到,StringBuffer的线程安全是使用synchronized关键字实现的!

问题2: StringBuffer和StringBuilder的扩容问题
其实这两个类底层的实现也基本相同,除了StringBuffer加了synchronized关键字,都用了父类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) {//minimumCapacity为当前字符串长度加上追加的字符串长度  
    // overflow-conscious code  
    if (minimumCapacity - value.length > 0)//最小需要的字符串长度大于当前的容量的话,进行扩容!  
        expandCapacity(minimumCapacity); //扩容函数  
}  

扩容函数:

void expandCapacity(int minimumCapacity) {  
    int newCapacity = value.length * 2 + 2;//第一次扩容,当前长度*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);//进行字符数组的复制,完成扩容  
}  

Arrays.copyOf底层使用的是java.lang.System.arraycopy(Object, int, Object, int, int)实现的:

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;  
} 
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容