Java字符串拼接

1. “+”号操作符

+号操作符是字符串拼接最常用的一种了

String str1 = "爱星星的";
String str2 = "阿狸";
System.out.println(str1 + str2);

把这段代码使用 JAD 反编译


原来编译的时候把“+”号操作符替换成了 StringBuilder 的 append 方法。也就是说,“+”号操作符在拼接字符串的时候只是一种形式主义,让开发者使用起来比较简便,代码看起来比较简洁,读起来比较顺畅。算是 Java 的一种语法糖吧。

2.StringBuilder

StringBuilder 的 append 方法就是第二个常用的字符串拼接姿势
StringBuilder 类的 append 方法的源码

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

通过super.append跳转到父类的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;
    }

①判断拼接的字符串是不是 null,如果是,当做字符串“null”来处理。appendNull 方法的源码

    private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    }

②拼接后的字符数组长度是否超过当前值,如果超过,进行扩容并复制。ensureCapacityInternal方法的源码如下

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

③将拼接的字符串 str 复制到目标数组 value 中。

str.getChars(0, len, value, count);

3.StringBuffer

先有 StringBuffer 后有 StringBuilder只不过大哥 StringBuffer 因为多呼吸两口新鲜空气,所以是线程安全的。

StringBuffer 类的 append 方法比 StringBuilder 多了一个关键字 synchronized

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

这个toStringCache字段是为了作缓存的.
缓存什么呢? 缓存最后一次toString的内容. 当被修改的时候这个cache清空.
也就是说, 如果没被修改, 那么这个toStringCache就是上一次toString的结果.
没被修改的时候, 就可以直接把toStringCache作为new String的参数. 然后把这个String返回就行了. 也就是cache有效的时候, 就不必进行arraycopy的复制操作. cache失效了才进行arraycopy的复制操作.
toString方法代码

    @Override
    public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
    }

4.String 类的 concat 方法

String 类的 concat 方法就好像 StringBuilder 类的 append

String str1 = "爱星星的";
String str2 = "阿狸";
System.out.println(str1.concat(str2));

那和append有什么区别呢?
查看源码

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

①如果拼接的字符串的长度为 0,那么返回拼接前的字符串。
②将原字符串的字符数组 value 复制到变量 buf 数组中。
char buf[] = Arrays.copyOf(value, len + otherLen);
③把拼接的字符串 str 复制到字符数组 buf 中,并返回新的字符串对象。

str.getChars(buf, len);
return new String(buf, true);

concat()底层是依靠Arrays.copyOf()方法实现的
主要区别

  • concat()方法:String类的concat()方法(只能用于拼接字符串,不能拼接其他类型的数据)将指定的字符串拼接到该字符串的末尾。并且字符串本身和拼接的字符串都不能为null,否则运行程序后会报空指针异常NullPointerException(编译时没有报错)。
  • append()方法:可以对字符,数字,字符串等数据类型的拼接,结果返回一个StringBuffer类型的对象

为何会抛出异常NullPointerException
String str2 = null;
System.out.println(str2.length());
//java.lang.NullPointerException
而append方法则重新用了一个appendNull()方法来处理空

5.String 类的 join 方法

String str1 = "爱星星的";
String str2 = "阿狸";
System.out.println(String.join("-",str1,str2));//爱星星的-阿狸

第一个参数为字符串连接符
join 方法的源码

    public static String join(CharSequence delimiter, CharSequence... elements) {
        Objects.requireNonNull(delimiter);
        Objects.requireNonNull(elements);
        // Number of elements not likely worth Arrays.stream overhead.
        StringJoiner joiner = new StringJoiner(delimiter);
        for (CharSequence cs: elements) {
            joiner.add(cs);
        }
        return joiner.toString();
    }

发现了一个新类 StringJoiner
StringJoiner是java.util包中的一个类,用于构造一个由分隔符分隔的字符序列(可选)
1.StringJoine用法

StringJoiner sj = new StringJoiner("-");
sj.add("爱星星");
sj.add("的");
sj.add("阿狸");
System.out.println(sj.toString());

StringJoiner sj1 = new StringJoiner(":","[","]");
sj1.add("爱星星").add("的").add("阿狸");
System.out.println(sj1.toString());
/*
爱星星-的-阿狸
[爱星星:的:阿狸]
 */

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

    public StringJoiner(CharSequence delimiter) {
        this(delimiter, "", "");//只是把前缀和后缀设置为“”
    }

2.StringJoine原理
add方法源码

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

进入prepareBuilder()

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

StringJoiner其实就是依赖StringBuilder实现的
3.为什么需要StringJoiner
java doc


试想,在Java中,如果我们有这样一个List:

List list = new ArrayList<>(Arrays.asList("爱星星","的","阿狸"));

如果我们想要把他拼接成一个以下形式的字符串:爱星星-的-阿狸
可以通过以下方式
1

StringBuilder builder = new StringBuilder();
if (!list.isEmpty()) {
    builder.append(list.get(0));
    for (int i = 1, n = list.size(); i < n; i++) {
        builder.append("-").append(list.get(i));
    }
}
System.out.println(builder.toString());//爱星星-的-阿狸

2

String res = list.stream().reduce(new StringBuilder(), (sb, s) -> sb.append(s).append('-'), StringBuilder::append).toString();
System.out.println(res);//爱星星-的-阿狸-

但是输出结果稍有些不同后面多了一个横线,需要进行二次处理:爱星星-的-阿狸-
3

还可以使用”+”进行拼接
 String res2 = list.stream().reduce((a,b)->a + "-" + b).get();
 System.out.println(res2);

以上几种方式,要么是代码复杂,要么是性能不高,或者无法直接得到想要的结果
为了满足类似这样的需求,Java 8中提供的StringJoiner就派上用场了

list.stream().collect(Collectors.joining(":"))

Collectors.joining的源代码

   public static Collector joining(CharSequence delimiter,
                                                             CharSequence prefix,
                                                             CharSequence suffix) {
        return new CollectorImpl<>(
                () -> new StringJoiner(delimiter, prefix, suffix),
                StringJoiner::add, StringJoiner::merge,
                StringJoiner::toString, CH_NOID);
    }

其实现原理就是借助了StringJoiner。

6.StringUtils.join

org.apache.commons.lang3.StringUtils,该类的 join 方法是字符串拼接的一种新姿势

String str1 = "爱星星的";
String str2 = "阿狸";
System.out.println(StringUtils.join(str1,str2));

该方法更善于拼接数组中的字符串,并且不用担心 NullPointerException
第一个参数是传入一个任意类型数组或集合,第二个参数是拼接符

String[] str = {"1","2","3","4"};
String str2 = StringUtils.join(str, "|");
System.out.println(str2);//1|2|3|4

StringUtils.join()可以传入Integer或者其他类型的集合或数组,
String.join()仅可以传入实现charSequence接口
(一个可读的字符序列,如String、StringBuilder、CharArray等都实现了该接口)类型的集合或数组。

查看源码StringUtils.join发现其内部使用的仍然是 StringBuilder。

public static String join(Object[] array, String separator, int startIndex, int endIndex) {
        if (array == null) {
            return null;
        } else {
            if (separator == null) {
                separator = "";
            }

            int noOfItems = endIndex - startIndex;
            if (noOfItems <= 0) {
                return "";
            } else {
                StringBuilder buf = newStringBuilder(noOfItems);

                for(int i = startIndex; i < endIndex; ++i) {
                    if (i > startIndex) {
                        buf.append(separator);
                    }

                    if (array[i] != null) {
                        buf.append(array[i]);
                    }
                }

                return buf.toString();
            }
        }
    }

总结

Java 8中提供的可变字符串类——StringJoiner,可以用于字符串拼接。
StringJoiner其实是通过StringBuilder实现的,所以他的性能和StringBuilder差不多,他也是非线程安全的。
如果日常开发中中,需要进行字符串拼接,如何选择?
1、如果只是简单的字符串拼接,考虑直接使用”+”即可。
2、如果是在for循环中进行字符串拼接,考虑使用StringBuilder和StringBuffer。
4、如果在并发场景中进行字符串拼接的话,要使用StringBuffer来代替StringBuilder
5、如果是通过一个List进行字符串拼接,则考虑使用StringJoiner。
6、如果是一个其他类型的数组拼接,可以使用工具StringUtils.join

为什么阿里巴巴不建议在 for 循环中使用”+”号操作符进行字符串拼接

使用for 循环中+号创建了大量的 StringBuilder 对象,
而第二段代码至始至终只有一个 StringBuilder 对象

参考文章
https://mp.weixin.qq.com/s/eswzUH03GfzGV5iAzpZLuw
https://www.hollischuang.com/archives/3283

你可能感兴趣的:(Java字符串拼接)