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 循环中
+
号创建了大量的 StringBuilder 对象,而第二段代码至始至终只有一个 StringBuilder 对象
参考文章
https://mp.weixin.qq.com/s/eswzUH03GfzGV5iAzpZLuw
https://www.hollischuang.com/archives/3283
网友评论