字符串学习

最近看了极客时间的String、StringBuilder和StringBuffer,想通过记录加深一下印象。

String

  • 1、字符串拼接

    String 类由于他的属性被声明成final类型,所以String声明的字符串是不可变的,每次对字符串内容进行操作,他都会生成一份新的String对象。这种方式的好处就是保护了原数据不会被更改,当使用赋值拷贝构造函数时,不会产生中间对象,所以这种场景比较适合String声明字符串。但是坏处也是显而易见的,如果出现大量的对字符串的操作,这种方式就会产生大量的需要回收的垃圾对象。所以不适合有很多对字符串操作的场景。基于此种情况,Java也对其进行了优化,比如对$+$号进行了重载,举个例子:

1
2
3
4
5
6
7
public class StringDemo {
    public static void main(String[] args) {
        String a = "123";
        String s = a+"abc"+"456";
        System.out.println(s);
    }
}

在jdk8版本里,字符串的拼接会转化成StringBuilder.append操作,在jdk9以后的版本 会依靠动态代理与javac生成的字节码进行解耦。

  • 2、字符串缓存 (这里之后再进行详细的了解吧)

StringBuilder和StringBuffer

StringBuilder和StringBuffer,这两个类继承自同一父类AbstractStringBuider,区别在于StringBuffer的所有方法都添加了synchronized关键字,是所有操作都是线程安全的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class StringDemo {
    public static void main(String[] args) {
        long n = 10000000;
        StringBuilder sb = new StringBuilder("123");
//        StringBuffer sb = new StringBuffer("123");
        long startTime = System.currentTimeMillis();
        while (n>0){
            sb.reverse();
            n--;
        }
        long endTime = System.currentTimeMillis();
        long result = endTime - startTime;
        System.out.println(result);
    }
}

我通过对StringBuilder和StringBuffer进行相同的操作,并且进行$10000000$次,次数太少显现不出差距,不带synchronized的reverse()操作大概30多毫秒,而带synchronized的reverse()的操作大概四十多毫秒,相差10毫秒,所以除非有对线程安全的需要,不然还是使用StringBuilder较好。

StringBuilder和StringBuffer的扩容

1
2
3
    public StringBuilder() {
        super(16);
    }

StringBuilder默认初始化的大小为16,StringBuffer也是一样。

1
2
3
4
5
6
7
8
9
10
public AbstractStringBuilder append(String str) {
    if (str == null) {
        return appendNull();
    }
    int len = str.length();
    ensureCapacityInternal(count + len);
    putStringAt(count, str);
    count += len;
    return this;
}

这是AbstractStringBuilder的append操作,其中的ensureCapacityInternal()是确保数组容量能够存储下拼接后的数组,如果不足会进行扩容。

1
2
3
4
5
6
7
8
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        int oldCapacity = value.length >> coder;
        if (minimumCapacity - oldCapacity > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity) << coder);
        }
    }

其中coder是编码格式,早先的版本采用char数组存储字符,但是由于空间利用率不高,改为采用byte数组存储,但是由于不同编码格式所占用字节数不同,所以使用变量coder进行标记(这里可以通过设置VM参数-XX:-CompactStrings进行改变coder的值)。上面的ensureCapacityInternal()函数会将原来的byte数组拷贝到扩容后的数组,并将原来的数组丢弃,所以在使用StringBuilder或者StringBuffer时,如果知道大概需要多长的数组,最好提前声明好,不然后期可能有很多次扩容开销。