JDK1.8特性之StringJoiner

本文详细介绍了JDK1.8中String、StringBuilder、StringBuffer的区别,强调了StringBuilder和StringBuffer的可变性以及在多线程环境下的选择。特别提到了JDK1.8引入的StringJoiner,它提供了更灵活的字符串拼接方式,包括设置前缀、后缀和空值处理。此外,还对比了StringJoiner与String.join()的功能,指出在不同场景下如何选择合适的字符串拼接方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

StringBuilder、StringBuffer、String这三个的区别,很多文章都有在说。
这边也给大家做一个简要的概述

一:String、StringBuilder、StringBuffer

1:String

String类型是不可变对象,所以我们在每次对 String 类型进行改变的时候,其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,不仅效率低下,而且大量浪费有限的内存空间,所以经常改变内容的字符串最好不要用 String 。
见源码:
在这里插入图片描述
final修饰符
修饰类:不能被继承,
修饰变量:表示该属性一旦被初始化便不可改变,这里不可改变的意思对基本类型来说是其值不可变,而对对象属性来说其引用不可再变。
修饰方法:说明这种方法提供的功能已经满足当前要求,不需要进行扩展,并且也不允许任何从此类继承的类来重写这种方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。在声明类中,一个 final 方法只被实现一次。

2:StringBuffer

在使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,所以如果需要对字符串进行修改推荐使用 StringBuffer;线程安全

3:StringBuilder

StringBuilder是JDK1.5新增的,注意:不保证同步,该类的设计是用作在StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。

4:StringBuffer和StringBuilder为什么可变

见String源码,我们发现
String对象的底层实际上是一个char[]数组:
如图一可见;
用final修饰的对象值可变,但是引用不变,即:value指向不可变,但是value[]数组的值可变,但因为有private关键字对其进行封装达到value[]数组值也不可变的目的

见StringBuffer源码
在这里插入图片描述
我们打开父类
在这里插入图片描述
可以清晰的看出StringBuffer的value[]没有被private final 修饰
那说明值是可以怎样的,可以被改变的哦。
我们接着看append方法是如何实现改变字符串的
在这里插入图片描述
然后进入super父类的方法
我们看看当前的源码
在这里插入图片描述
str.getChars()方法即是将str的所有字符拷贝到value[]的后面,返回的还是原来的value数组。
StringBuilder同理。
在这里插入图片描述

5:StringBuffer和StringBuilder区别

大家都知道StringBuffer是线程安全的,StringBuilder是非线程安全的

StringBuilder在 Java1.5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,多数情况下建议使用 StringBuilder 类。在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

为什么StringBuffer是线程安全的呢?
见StringBuffer源码:
在这里插入图片描述
见StringBuilder源码:
在这里插入图片描述
是否出现了一个关键字synchronized,这个关键字大家都认识,这个关键字是为线程同步机制设定的;
这边大概举个例子,如果深究多线程的知识,自行学习
每一个类对象都对应了一把锁,当某个线程A调用类对象B中的synchronized方法C时,必须获得对象B的锁才能够执行C方法,否则线程A阻塞。一旦线程A开始执行C方法,将独占对象B的锁。使得其它需要调用B对象的C方法的线程阻塞。只有线程A执行完毕,释放锁后。那些阻塞线程才有机会重新调用C方法。这就是解决线程同步问题的锁机制。

当看到这个,一说到安全,那大家第一反应,那我就直接用StringBuffer就好了,多线程比StringBuilder安全,如果有多线程需要对同一个字符串缓冲区进行操作的时候,StringBuffer的确是首选。
这样是不是表明StringBuilder和String是不安全咯,String不可变的,线程对于堆中的一个String对象只能读取,不能修改。

注意:看源码我们发现StringBuilder是1.5出来的,说白点前身就是StringBuffer,不考虑线程安全,StringBuilder比StringBuffer效率高,StringBuilder应该是首选。另外,JVM运行程序主要的时间耗费是在创建对象和回收对象上。

6:String,StringBuffer,StringBuilder效率

接着我们看看String那要不要用
可以这样说,某些特别情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以某些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的:

如同下面的情况
String str = “this”+“is”+“a”+“java”;
StringBuffer stringBuffer = new StringBuffer(“this”).append(“is”),append(“a”).append("java);

你会发现,生成str对象会很快,而stringBuffer却不快,因为JVM会自动解析str为
String str = “this”+“is”+“a”+“java”;
String str = “this is a java”;
注意:慢的是对象,就是来自另外的String对象,如下
String str1 = “this”;
String str2 = “is”;
String str3 = “a”;
String str4 = “java”;
这种情况JVM 会正常的按照原来的方式去做。

正常情况下,StringBuffer比String快。
因为StringBuffer有字符串缓冲区的概念。

7:总结三者区别

描述是否可变是否线程安全多线程或单线程操作字符串
StringString的值是不可变的,那每次对String的操作都会生成新的String对象,效率低下,且浪费大量优先的内存空间不可变
StringBufferStringBuffer是可变的,由于大量的方法用synchronized修饰,属于线程安全,任何对它指向的字符串的操作都不会产生新的对象。可变多线程
StringBuilderStringBuilder是1.5引入的,非线程安全的,效率比StringBuffer高。任何对它指向的字符串的操作都不会产生新的对象。可变单线程

如果我们在编译阶段能确定的字符串常量,完全没有必要创建String或StringBuffer、StringBuilder对象。可以直接的使用字符串常量的"+“连接操作效率最高。
StringBuffer对象的append效率要远远高于String对象的”+"连接操作。

大致了解了三者区别,具体涉及到class文件,这边没有多做概述,我们现在看看为什么有了StringBuilder,StringBuffer,JDK1.8引入了StringJoiner。

二:StringJoiner

我们经常拼接字符串,考虑线程安全我们就用StringBuffer,非线程安全我们就用StringBuilder。

现在JDK1.8拼接神器StringJoiner来了。
先上个例子:

StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(hi);
stringBuilder.append(",");
stringBuilder.append("python");
stringBuilder.append(",");
stringBuilder.append("i am java");
System.out.println(stringBuilder.toString());

重复拼接的逗号(),看起来是不是很傻瓜式的样子,我们看看StringJoiner是如何做的。

StringJoiner stringJoiner = new StringJoiner(",");
 stringJoiner.add("hi");
 stringJoiner.add("python");
 stringJoiner.add("i am java");
  System.out.println(stringJoiner.toString());

我们直接见源码
首先观看构造方法
在这里插入图片描述
默认提供了两种构造方法,一个是需要传分隔符的,一个是需要传分隔符,前缀,后缀,我们清楚看到
this.emptyValue = this.prefix + this.suffix;
emptyValue 默认是前缀+后缀组成。

先解释下成员变量
prefix:拼接后的字符串前缀
delimiter:拼接时的字符串分隔符
suffix:拼接后的字符串后缀
value:拼接后的值
emptyValue:空值的情况,value为 null 时返回

看看它给我们提供了哪些可以公共调用的方法,
setEmptyValue 设置空值
在这里插入图片描述
toString 主要用于转换String
在这里插入图片描述
add 用于添加字符串
在这里插入图片描述
merge 从另外一个StringJoiner进行合并
在这里插入图片描述
length 长度
在这里插入图片描述
上面我们用到了add方法
见源码
在这里插入图片描述
在这里插入图片描述
StringBuilder,可以清晰的看到,底层还是根据StringBuilder进行封装的,看看多了一个prefix没有,首先它会先添加一个前缀。然后分隔符,字符串

看返回对象仍是StringJoiner,所以后面我们有需求,仍然可以用流式处理。

我们刚才模拟了分隔符,现在我们加上前后缀作为例子看看

StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("[“);
stringBuilder.append(hi);
stringBuilder.append(",");
stringBuilder.append("python");
stringBuilder.append(",");
stringBuilder.append("i am java");
stringBuilder.append("]“);

System.out.println(stringBuilder.toString());// [hi,python,i am java]


StringJoiner stringJoiner = new StringJoiner(",","[","]");// 参数一分隔符,参数二前缀,参数三后缀
 stringJoiner.add("hi");
 stringJoiner.add("python");
 stringJoiner.add("i am java");
  System.out.println(stringJoiner.toString());// [hi,python,i am java]

代码是不是很优雅

空值如何处理
// 构造方法1
 StringJoiner stringJoiner = new StringJoiner(",");
  System.out.println(stringJoiner.toString());
// 构造方法2
StringJoiner stringJoiner = new StringJoiner(",", "[", "]");

输出指定字符串
可以通过setEmptyValue 
 StringJoiner stringJoiner = new StringJoiner(",", "[", "]");
    stringJoiner.setEmptyValue("gaci");
    System.out.println(stringJoiner.toString());// gaci

三:String.join()

String.join() 主要针对 StringJoiner 又封装了一层的 API,也是Java1.8特性,可以传入动态参数或者迭代器。
见源码:
在这里插入图片描述
提供了两个方法:
动态参数
在这里插入图片描述
迭代器
在这里插入图片描述
仔细阅读之后,此API只能用于简单的拼接,不能添加前后缀以及空值处理。
System.out.println(String.join(",",“hi”,“python”,“i am java”));
在这里插入图片描述
的确方便了更多
使用拼间多个相同的分隔符时,使用 StringJoiner,简单处理使用 String.join() 也能完成;针对不同的场景使用不同的 API,这才是最佳最优雅的处理方式。

五:流式处理StringBuilder和StringJoiner

StringBuilder流式处理


final List<String> langs = Arrays.asList("java", "python", "php");

final String collectJoin = langs.stream().collect(Collectors.joining(", "));

StringBuilder:
final String collectBuilder =
    langs.stream().collect(Collector.of(StringBuilder::new,
        (stringBuilder, str) -> stringBuilder.append(str).append(", "),
        StringBuilder::append,
        StringBuilder::toString));

格式化输出:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
String num = numbers.stream()
    .map(i -> i.toString())
    .collect(Collectors.joining(", "))

StringJoiner是一种收集器,没有实现Collector接口。可以传递定界符,前缀和后缀之外,还可以使用StringJoiner调用Collectors.joining(CharSequence)从Stream创建格式化的输出。
当使用并行流时,这很有用,因为我们在某些时候,将需要并行处理的批处理,StringJoiner就发生了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值