Java 字符串拼接

Posted 刘润森!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 字符串拼接相关的知识,希望对你有一定的参考价值。

前言

本文参考网上的一些博文,结合自身的开发经验,整理出一些字符串拼接的使用建议。
版本约定

  • JDK 版本:1.8.0_231
  • Java SE API Documentation:https://docs.oracle.com/javase/8/docs/api/

正文

字符串不变性与字符串拼接

,我们知道字符串是不可变的。所以字符串的拼接,都是重新生成一个新的字符串。比如下面这段字符串拼接代码。

public static void main(String[] args) 
    String s = "abcd";
    s = s.concat("ef");

其实最后我们得到的 s 已经是一个新的字符串了。如下图:


s 中保存的是一个重新创建出来的 String 对象的引用。

符号 ‘+’

在 Java 中,拼接字符串最简单的方式就是直接使用符号 ‘+’ 来拼接。

public static void main(String[] args) 
    String message = "User's age: ";
    int age = 18;
    System.out.println(message + age);

运行程序,输出:

User's age: 18

‘+’ 号按照给定的次序将两个字符串拼接起来。我们注意到,当将一个字符串与一个非字符串的值进行拼接时,后者会被转换成字符串。

在 Java 中,任何一个 Java 对象都可以转换成字符串,默认会调用 Java 对象的 toString() 方法生成 Java 对象的字符串。

一些简单的场景,或者说是不涉及到遍历生成字符串的场景都可以使用符号 ‘+’ 来拼接字符串。

实现原理

符号 ‘+’ 是 Java 提供的语法糖,我们不知道它是怎么实现的。只能先把 .java 文件编译成 .class 文件,然后通过反编译工具 Jad 反编译生成的 .class 文件,查看字节码文件中真正的实现。

public static void main(String args[])

    String message = "User's age: ";
    int age = 18;
    System.out.println((new StringBuilder()).append(message).append(age).toString());

如上所示,它是通过 StringBuilder 实现的。

concat

除了使用符号 ‘+’ 拼接字符串之外,还可以使用 String 类中 concat 方法来拼接字符串。

public static void main(String[] args) 
    String message = "User's age: ";
    int age = 18;
    System.out.println(message.concat(age + ""));

运行程序,输出:

User's age: 18

concat 方法的使用场景很少,一般用不到。

实现原理

我们通过 concat 的源码了解一下它的实现原理。

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

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

StringBuilder & StringBuffer

使用 StringBuilder 可以方便的对字符串进行拼接。

public static void main(String[] args) 
    String message = "User's age: ";
    int age = 18;
    StringBuilder sb = new StringBuilder();
    sb.append(message).append(age);
    System.out.println(sb.toString());

StringBuffer 的用法和 StringBuilder 类似,StringBuffer 是线程安全的,性能对比 StringBuilder,要差点,一般我们也不会使用。

实现原理

接下来我们看看 StringBuilder 和 StringBuffer 的实现原理。

StringBuilder 继承了 AbstractStringBuilder,和 String 类类似,AbstractStringBuilder 类也封装了一个字符数组,定义如下:

/**
 * The value is used for character storage.
 */
char[] value;

与 String 不同的是,它并不是 final 的,所以它是可以修改的。另外,它还提供了一个实例变量,用来记录数组中已经使用的字符个数。

/**
 * The count is the number of characters used.
 */
int count;

StringBuilder 的 append 方法重写了父类的方法,并且调用了父类的 append 方法。

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

AbstractStringBuilder 的 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;

append 方法内部还是通过字符数组拷贝实现的,如果字符数组长度不够,通过 ensureCapacityInternal 方法进行扩容。

StringBuffer 和 StringBuilder 类似,最大的区别就是 StringBuffer 是线程安全的,看一下 StringBuffer 的 append 方法。

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

该方法使用 synchronized 进行声明,说明是一个线程安全的方法。而 StringBuilder 则不是线程安全的。

String.join

Java 8 的 String 类中提供了一个静态方法:join,使用方式如下。

public static void main(String[] args) 
    String message = String.join("-", "Java", "is", "cool");
    System.out.println(message);

运行程序,输出:

Java-is-cool

该方法比较适合用来将字符串数组以固定的拼在这里插入代码片接符拼接到一起形成新的字符串。

实现原理

通过查看 String.join 方法的源码,我们发现,它是通过 StringJoiner 类实现的。

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 类的源码,它内部也是通过 StringBuilder 实现,受限于文章篇幅,这里就不列出来了。

StringJoiner 只是对 StringBuilder 的进一步封装,适合用来构造一个由固定分隔符分隔的字符序列,并且可以非常方便的添加前缀和后缀。

StringUtils.join

StringUtils 是 Apache Commons 中提供的字符串工具类,其中的静态方法 join 可以用来拼接字符串,用法和 String.join 方法类似。

public static void main(String[] args) 
    String[] arr = "Java", "is", "cool";
    String message = StringUtils.join(arr, "-");
    System.out.println(message);

总结

本文介绍了什么是字符串拼接,虽然字符串是不可变的,但是还是可以通过新建字符串的方式来进行字符串的拼接。

常用的字符串拼接方式有六种,分别是使用 ‘+’、concat、StringBuilder、StringBuffer、String.join 以及使用 StringUtils.join。

由于字符串拼接过程中会创建新的对象,所以如果要在一个循环体中进行字符串拼接,就要考虑内存问题和效率问题。

因此,经过对比,我们发现,直接使用 StringBuilder 的方式是效率最高的。因为 StringBuilder 天生就是设计来定义可变字符串和字符串的变化操作的。

但是,还要强调的是:

  1. 如果只是简单的字符串拼接,考虑直接使用 ‘+’ 即可;
  2. 如果是在 for 循环中进行字符串拼接,考虑使用 StringBuilder;
  3. 如果是通过一个 List 进行字符串拼接,则考虑使用 StringJoiner or String.join or StringUtils.join;
  4. 如果在并发场景中进行字符串拼接的话,要使用StringBuffer来代替StringBuilder。

以上是关于Java 字符串拼接的主要内容,如果未能解决你的问题,请参考以下文章

教妹学 Java:字符串拼接

羞,Java 字符串拼接竟然有这么多姿势

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

java字符串的拼接

java中如何解决sql字符串的拼接

Java 字符串拼接