JDK-StringJoiner构造及添加元素源码分析

 更新时间:2023年07月28日 11:50:52   作者:罗_宇  
这篇文章主要为大家介绍了JDK-StringJoiner构造及添加元素源码分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

背景

功能描述:

将多个元素使用指定符号前后连接为字符串;

eg:1 2 3 4 5 , => 1,2,3,4,5

要点:

  • 多个元素
  • 指定分隔符
  • 分隔符只在元素之间,不能作为第一或最后一个

使用方法:

// 1 构造 设置分隔符/前缀/后缀
StringJoiner joiner = new StringJoiner(",", "start", "end");
// 2 添加元素
List<String> elements = Arrays.asList("1","2","3","4")
for (String s: elements) {
    joiner.add(s);
}
// 3获取拼接后结果
joiner.toString(); // "start1,2,3,4end"

源代码

基础属性

public final class StringJoiner {
	// 前缀
    private final String prefix;
    // 分隔符
    private final String delimiter;
    // 后缀
    private final String suffix;
    // 拼接后的value,包含前缀、分隔符、和拼接元素,不包括后缀,后缀在toString返回前才会填充
    /*
     * StringBuilder value -- at any time, the characters constructed from the
     * prefix, the added element separated by the delimiter, but without the
     * suffix, so that we can more easily add elements without having to jigger
     * the suffix each time.
     */
    private StringBuilder value;
  	// 空值,默认为prefix+suffix,支持自定义设置
    private String emptyValue;
}

构造方法

public StringJoiner(CharSequence delimiter) {
    this(delimiter, "", "");
}

public StringJoiner(CharSequence delimiter,
                    CharSequence prefix,
                    CharSequence suffix) {
    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");
    // make defensive copies of arguments
    this.prefix = prefix.toString();
    this.delimiter = delimiter.toString();
    this.suffix = suffix.toString();
    this.emptyValue = this.prefix + this.suffix;
}

可以看到,构造方法只是简单的根据参数设置响应的属性,

值得注意的是:

this.emptyValue = this.prefix + this.suffix;

默认的 emptyValue 是由前缀和后缀简单拼接而成​

添加元素

public StringJoiner add(CharSequence newElement) {
    prepareBuilder().append(newElement);
    return this;
}
private StringBuilder prepareBuilder() {
    if (value != null) {
        value.append(delimiter);
    } else {
        value = new StringBuilder().append(prefix);
    }
    return value;
}

add 方法首先调用了 prepareBuilder 方法用来执行添加元素的前置操作 ;

prepareBuilder 内部会先判断 value 是否为空,若为空则构建StringBuilder对象且追加前缀,如果不为空则追加分隔符;

执行逻辑如下:

joiner.add("1");
---
prepareBuilder()			//  value = "start"
	.append("1");			//  value = "start1"
=========================================================
joiner.add("2");
---
prepareBuilder()			//  value = "start1,"
	.append("2");			//  value = "start1,2"
=========================================================
joiner.add("3");
---
prepareBuilder()			//  value = "start1,2,"
	.append("3");			//  value = "start1,2,3"

拼接结果

public String toString() {
    // 没有调用add方法添加元素,直接返回空值
    if (value == null) {
        return emptyValue;
    } else {
        // 没有后缀,直接将value.toString()即可
        if (suffix.equals("")) {
            return value.toString();
        } else {
            // 追加后缀后返回
            int initialLength = value.length();
            String result = value.append(suffix).toString();
            // reset value to pre-append initialLength
            value.setLength(initialLength);
            return result;
        }
    }
}

核心的逻辑的在:

String result = value.append(suffix).toString(); 

一句话描述该逻辑是:拼接后缀,然后把完整的结果返回;

疑问点

主要关注下追加后缀后得到结果的前后:

int initialLength = value.length(); // 获取value长度
String result = value.append(suffix).toString(); // value中追加后缀,得到最终结果
value.setLength(initialLength); // 长度重置回追加后缀前的长度,可以理解为将原长度之后字符清空
return result;	// 返回拼接完成的结果
  • 获取value的长度
  • 添加后缀生成最终的结果result
  • 将value长度重置回添加后缀之前
  • 返回结果

只需要2就拿到结果了,为什么要1和3?如果把1和3去掉,会怎么样?

StringJoiner joiner = new StringJoiner(",", "start", "end");
List&lt;String&gt; elements = Arrays.asList("1","2","3","4")
for (String s: elements) {
    joiner.add(s);
}
joiner.toString(); // "start1,2,3,4end"
joiner.add("5"); 
joiner.toString(); // "start1,2,3,4end,5end"

在StringJoiner#toString方法中用于拿到完整结果临时填充进去后缀没有被去掉,污染了后续使用joiner对象;

显而易见,1&3步骤是为了清理临时填充的后缀"end"。

清理怎么实现的? setLength设置长度就好了?

看下StringBuilder中的setLength的实现:

//java.lang.AbstractStringBuilder#setLength
public void setLength(int newLength) {
    if (newLength < 0)
        throw new StringIndexOutOfBoundsException(newLength);
    ensureCapacityInternal(newLength);
    if (count < newLength) {
        Arrays.fill(value, count, newLength, '\0');
    }
    // 设置count为填充后缀之前的长度值
    count = newLength;
}
// java.lang.StringBuilder#toString
public String toString() {
    // 使用value[0,count]位置的字符生成结果
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

可以看到,setLength方法是将原长度值赋值给count属性;count是StringBuilder中维护用来记录填充字符数量的属性;

StringBuilder#append会从count的位置向后操作value数组;StringBuilder#toString方法调用时也会只会使用[0,count]位置的字符生成字符串结果;

至此核心源码分析就结束了;

新姿势

对于有状态的对象,幂等方法内如果要临时改动内部状态,逻辑完成后需要将变动的属性恢复原貌;

对于使用者而言,StringJoiner#toString方法应该是幂等的,不能对当前象内的数据状态做任何变动,这样才能保证得到的结果是一致的;

再回顾下StringJoiner#value上面的注释:

at any time, the characters constructed from the prefix, the added element separated by the delimiter, but without the suffix

处理数组&列表等数据时,可以标记删除,性能更好;删除数据时不一定要把指定位置数据清理掉,使用used_index标记有效位置,删除时修改下used_index的方法会更高效;

以上就是JDK-StringJoiner构造及添加元素源码分析的详细内容,更多关于JDK-StringJoiner源码分析的资料请关注脚本之家其它相关文章!

相关文章

  • Java实现将png格式图片转换成jpg格式图片的方法【测试可用】

    Java实现将png格式图片转换成jpg格式图片的方法【测试可用】

    这篇文章主要介绍了Java实现将png格式图片转换成jpg格式图片的方法,涉及java文件读写及图形创建等相关操作技巧,需要的朋友可以参考下
    2018-03-03
  • java实现压缩字符串和java字符串过滤

    java实现压缩字符串和java字符串过滤

    这篇文章主要介绍了java实现压缩字符串和java字符串过滤,需要的朋友可以参考下
    2014-04-04
  • 一篇文中细看Java多线程的创建方式

    一篇文中细看Java多线程的创建方式

    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发,这篇文章主要给大家介绍了如何通过一篇文中细看Java多线程的创建方式,需要的朋友可以参考下
    2021-07-07
  • Java中双冒号运算符(::)的用法详解

    Java中双冒号运算符(::)的用法详解

    在Java 8引入的Lambda表达式和函数式接口之后,双冒号运算符(::)成为了一项重要的功能,下面我们就来学习一下Java中的双冒号运算符及其常见应用场景吧
    2023-12-12
  • 一招教你优化Java代码中大量的if/else

    一招教你优化Java代码中大量的if/else

    当代码已经复杂到难以维护的程度之后,只能狠下心重构优化。那,有什么方案可以优雅的优化掉这些多余的if/else?本文就来和大家详细聊聊
    2023-03-03
  • Java类加载策略之双亲委派机制全面分析讲解

    Java类加载策略之双亲委派机制全面分析讲解

    这篇文章主要介绍了Java双亲委派机制,双亲委派模型是Java加载类的机制,采用双亲委派模型的好处是Java类随着它的类加载器一起具备了一种带有优先级的层级关系,通过这种层级关系可以避免类的重复加载,感兴趣的朋友可以参考下
    2023-12-12
  • MyBatis连接池的深入和动态SQL详解

    MyBatis连接池的深入和动态SQL详解

    这篇文章主要介绍了MyBatis连接池的深入和动态SQL详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • 如何用ObjectMapper将复杂Map转换为实体类

    如何用ObjectMapper将复杂Map转换为实体类

    这篇文章主要介绍了如何用ObjectMapper将复杂Map转换为实体类的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java 模拟cookie登陆简单操作示例

    Java 模拟cookie登陆简单操作示例

    这篇文章主要介绍了Java 模拟cookie登陆简单操作,结合实例形式分析了Java 模拟cookie登陆的相关原理与基本实现技巧,需要的朋友可以参考下
    2020-03-03
  • springboot配置mybatis和事务管理方式

    springboot配置mybatis和事务管理方式

    这篇文章主要介绍了springboot配置mybatis和事务管理方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04

最新评论