前置知识

  1. String是java中的不可变类,一旦被实例化就无法再修改

  2. java不支持运算符重载


常见的字符串拼接方法有 使用符号‘+’拼接、使用String类中的concat方法拼接、使用StringBuffer拼接、使用StringBuilder拼接、StringUtils.join

使用符号‘+’拼接

使用+拼接字符串,其实只是Java提供的一个语法糖,其实现原理是StringBuilder.append

// 使用符号‘+’拼接字符串
String hollis = wechat + "," + introduce;

// 上面代码的反编译结果
String hollis = (new StringBuilder()).append(wechat).append(",").append(introduce).toString();

从反编译后的代码,使用+拼接字符串每次都是new了一个StringBuilder,然后再把String转成StringBuilder,再进行append。

如果在for循环中使用+拼接字符串,会频繁的new一个对象,不仅仅会耗费时间,还会造成内存资源的浪费。

所以,根据阿里巴巴Java开发手册建议:循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展,而不要使用+。

使用String类中的concat方法拼接

用法

String hollis = "wechat".concat(",").concat("introduce");

String类中concat方法的源码

public String concat(String str) {
    if (str.isEmpty()) {
        return this;
    }
    int len = value.length;
    int otherLen = str.length();
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    return new String(buf, true);
}

可以看到,concat方法首先创建了一个字符数组,长度是已有字符串和待拼接字符串的长度之和,再把两个字符串的值复制到新的字符数组中,并使用这个字符数组创建一个新的 String 对象并返回。

通过源码我们也可以看到,经过 concat 方法,其实是new了一个新的 String,这也就呼应到前面我们说的字符串的不变性问题上了。

使用StringBuffer和StringBuilder拼接

StringBuffer和StringBuilder都继承了AbstractStringBuilder类,在AbstractStringBuilder类中定义了一个字符数组 char[] value ,与String类中不同的是,它没有final修饰符,所以是可以被修改的
StringBuffer 和 StringBuilder 最大的区别就是 StringBuffer是线程安全的,StringBuffer使用synchronized进行声明,重写了AbstractStringBuilder类中的部分方法,

@Override
    public synchronized int length() {
        return count;
    }

StringUtils.join

Apache(version3.8)的StringUtils.join的源码

public static String join(final char[] array, final char separator, final int startIndex, final int endIndex) {
    if (array == null) {
        return null;
    }
    final int noOfItems = endIndex - startIndex;
    if (noOfItems <= 0) {
        return EMPTY;
    }
    final StringBuilder buf = newStringBuilder(noOfItems);
    for (int i = startIndex; i < endIndex; i++) {
        if (i > startIndex) {
            buf.append(separator);
        }
        buf.append(array[i]);
    }
    return buf.toString();
}

可以看到,StringUtils.join是通过StringBuffer实现的,其最主要的功能是 将数组或集合以某拼接符拼接到一起形成新的字符串

StringJoiner

StringJoiner 是 java.util包中的一个类,用于构造一个由分隔符分隔的字符序列(可选),并且可以从提供的前缀开始并以提供的后缀结尾。

用法

public class StringJoinerTest {

    public static void main(String[] args) {
        StringJoiner sj = new StringJoiner("Hollis");    // Hollis是分隔符

        sj.add("hollischuang");
        sj.add("Java干货");
        System.out.println(sj.toString());

        StringJoiner sj1 = new StringJoiner(":","[","]");    // StringJoiner(CharSequence delimiter,CharSequence prefix,CharSequence suffix)

        sj1.add("Hollis").add("hollischuang").add("Java干货");
        System.out.println(sj1.toString());
    }
}

// 以上代码返回结果
// hollischuangHollisJava干货
// [Hollis:hollischuang:Java干货]

需要注意的是,当我们StringJoiner(CharSequence delimiter)初始化一个StringJoiner的时候,这个delimiter其实是分隔符,并不是可变字符串的初始值。

根据StringJoiner.add方法的源码,可以看到,其实现原理也是依赖的StringBuilder

public StringJoiner add(CharSequence newElement) {
    prepareBuilder().append(newElement);
    return this;
}

private StringBuilder prepareBuilder() {
    if (value != null) {
        value.append(delimiter);
    } else {
        value = new StringBuilder().append(prefix);
    }
    return value;
}

list.stream().collect(Collectors.joining(","))也是借助StringJoiner类实现的列表拼接字符串,但是使用StringJoiner类可以方便地增加前缀和后缀,适用于字符串拼接有前、后缀的场景。


如果日常开发中,需要进行字符串拼接,如何选择?

  • 如果只是简单的字符串拼接,不是在循环体中进行字符串拼接的话,直接使用+就好了
  • 如果是在 for 循环中进行字符串拼接,考虑使用StringBuilder和StringBuffer
  • 如果在并发场景中进行字符串拼接的话,要使用StringBuffer来代替StringBuilder
  • 如果是通过一个List进行字符串拼接,则考虑使用StringUtils.join和StringJoiner

参考文章

关于 Java 字符串拼接的几种方式以及性能比较

03-05 15:34