美文网首页安卓
Java字符串拼接

Java字符串拼接

作者: 盼旺 | 来源:发表于2019-10-23 11:12 被阅读0次

    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<String> 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<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);
        }
    

    其实现原理就是借助了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字符串拼接

        本文链接:https://www.haomeiwen.com/subject/izxxvctx.html