龙空技术网

拼接字符串新姿势—StringJoiner

Java架构学习指南 187

前言:

当前大家对“java 字符串数组拼接”都比较注重,朋友们都想要剖析一些“java 字符串数组拼接”的相关资讯。那么小编同时在网上收集了一些有关“java 字符串数组拼接””的相关文章,希望同学们能喜欢,姐妹们快快来了解一下吧!

前言

你知道使用原生 JDK 拼接字符串有多少种玩法吗?我们能想到的大概就是StringBuilder、StringBuffer、 String.concat、还有用 + 拼接。还有其他的吗?接下来就来讲讲 Java 8 里面新增了一个拼接字符串的类 ,叫 StringJoiner 的用法。

概述

StringJoiner 是 java.util 包中的一个类,用于构造一个由分隔符分隔的字符串,并且可以从提供的前缀字符串开头,以提供的后缀字符串结尾。虽然这些功能,我们可以使用 StringBuilder 或者 StringBuffer 来实现的, 但是 StringJoiner 提供的方法更简单,而且也不需要写大量的代码就能成。

构造函数

StringJoiner 一共有 2 个构造函数。 构造函数很简单,就是对 分隔符,前缀和后缀字符串的初始化。

    public StringJoiner(CharSequence delimiter) {        this(delimiter, "", "");//默认前缀和后缀为"",重载调用    }    public StringJoiner(CharSequence delimiter,                        CharSequence prefix,                        CharSequence suffix) {        //间隔符,前缀和后缀判断是否为null,null将抛出异常        Objects.requireNonNull(prefix, "The prefix must not be null");        Objects.requireNonNull(delimiter, "The delimiter must not be null");        Objects.requireNonNull(suffix, "The suffix must not be null");        //成员变量赋值        this.prefix = prefix.toString();        this.delimiter = delimiter.toString();        this.suffix = suffix.toString();        //空值被设置为只有前后缀        this.emptyValue = this.prefix + this.suffix;    }
用法

StringJoiner 的用法其实很简单,来,我们看一下 StringJoiner 字符串拼接的用法。

public class StringJoinerTest {    public static void main(String[] args) {        StringJoiner sj = new StringJoiner("hello");        sj.add("Java 8 ");        sj.add("Java 11");        System.out.println(sj.toString());        StringJoiner sj1 = new StringJoiner(":","[","]");        sj1.add("Java8").add("Java11").add("Java15");        System.out.println(sj1.toString());    }}

输出结果:

Java 8 helloJava 11[Java8:Java11:Java15]

注意:

当我们使用StringJoiner(CharSequence delimiter)初始化一个StringJoiner的时候,这个delimiter其实是分隔符,并不是字符串的初始值。StringJoiner(CharSequence delimiter,CharSequence prefix,CharSequence suffix)的第二个和第三个参数分别是拼接后的字符串的前缀和后缀。源码分析

JDK8中源码

  public StringJoiner setEmptyValue(CharSequence emptyValue) {        this.emptyValue = Objects.requireNonNull(emptyValue,            "The empty value must not be null").toString();        return this;    }    @Override    public String toString() {        if (value == null) {            return emptyValue;        } else {            if (suffix.equals("")) {                return value.toString();            } else {                int initialLength = value.length();                String result = value.append(suffix).toString();                value.setLength(initialLength);                return result;            }        }    }    public StringJoiner add(CharSequence newElement) {        prepareBuilder().append(newElement);        return this;    }    public StringJoiner merge(StringJoiner other) {        Objects.requireNonNull(other);        if (other.value != null) {            final int length = other.value.length();            StringBuilder builder = prepareBuilder();            builder.append(other.value, other.prefix.length(), length);        }        return this;    }    private StringBuilder prepareBuilder() {        if (value != null) {            value.append(delimiter);        } else {            value = new StringBuilder().append(prefix);        }        return value;    }    public int length() {        return (value != null ? value.length() + suffix.length() :                emptyValue.length());    }

JDK11中源码

public final class StringJoiner {    private String[] elts;    @Override    public String toString() {        final String[] elts = this.elts;        if (elts == null && emptyValue != null) {            return emptyValue;        }        final int size = this.size;        final int addLen = prefix.length() + suffix.length();        if (addLen == 0) {            compactElts();            return size == 0 ? "" : elts[0];        }        final String delimiter = this.delimiter;        final char[] chars = new char[len + addLen];        int k = getChars(prefix, chars, 0);        if (size > 0) {            k += getChars(elts[0], chars, k);            for (int i = 1; i < size; i++) {                k += getChars(delimiter, chars, k);                k += getChars(elts[i], chars, k);            }        }        k += getChars(suffix, chars, k);        return new String(chars);    }    public StringJoiner add(CharSequence newElement) {        final String elt = String.valueOf(newElement);        if (elts == null) {            elts = new String[8];        } else {            if (size == elts.length)                elts = Arrays.copyOf(elts, 2 * size);            len += delimiter.length();        }        len += elt.length();        elts[size++] = elt;        return this;    }}

比较一下JDK8和JDK11中StringJoiner的源码add 方法的实现:

JDK8中StringJoiner 是通过 StringBuilder 来实现的;JDK11中StringJoiner 就是把待拼接的字符串,放到一个字符串数组里面,toString() 方法的时候,才是真正做字符串拼接的过程。

那么问题来了,既然 JDK 8 的时候,已经使用了StringBuilder 来实现,JDK11为什么还要改成 String[] 来缓存所有的待拼接的字符串。这个就要涉及到JVM底层的优化了。

既然已经有了StringBuilder,为什么还要造一个StringJoiner,它的优势到底在哪里,那我们再来找找原因,我们可以看到代码的注释块中,标记了 Collectors#joining 。

我们看看 Collectors#joining 究竟做了什么呢?

    public static Collector<CharSequence, ?, String> joining() {        return new CollectorImpl<CharSequence, StringBuilder, String>(                StringBuilder::new, StringBuilder::append,                (r1, r2) -> { r1.append(r2); return r1; },                StringBuilder::toString, CH_NOID);    }    public static Collector<CharSequence, ?, String> joining(CharSequence delimiter) {        return joining(delimiter, "", "");    }    public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,                                                             CharSequence prefix,                                                             CharSequence suffix) {        return new CollectorImpl<>(                () -> new StringJoiner(delimiter, prefix, suffix),                StringJoiner::add, StringJoiner::merge,                StringJoiner::toString, CH_NOID);    }

原来 Java 8 中Stream 是借助了 StringJoiner 来实现的。 这个时候,我们可能会想,为什么不使用 StringBuilder 来实现呢? 我们可以从示例里看出,如果 StringBuilder 来构造拼接的话,在没有前后缀的情况下,应该还是简单的,但是一旦到需要其他前后缀的拼接,那这个就变得很复杂啦。

所以 StringJoiner 在 Java 8 的地位是 StringBuilder 所不能代替的。

总结

本文介绍了 Java 8 开始提供的字符串拼接类 StringJoiner。 JDK 8 中 StringJoiner 是通过 StringBuilder 实现的, 所以它的性能和StringBuilder 差不多,它也是非线程安全的。JDK 11 中已经对其进行了优化,通过 String[] 来代理 StringBuilder 。

在日常的开发过程中,我们怎么选择字符串拼接类呢?

简单的字符串拼接,直接使用 + 即可。在 for 循环之类的场景下需要字符串拼接,可以优先考虑使用 StringBuilder 。在使用 Java Stream 和 lambda 的场景下需要字符串拼接,可以优先考虑使用 StringJoiner。

作为阅读福利我也整理了一些Java学习笔记+面试真题,现在免费分享给阅读到本篇文章的Java程序员朋友们,需要的后台私信【资料领取】即可获得!!

标签: #java 字符串数组拼接