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<String> 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中的SynchronousQueue阻塞队列及使用场景解析
这篇文章主要介绍了Java中的SynchronousQueue阻塞队列及使用场景解析,SynchronousQueue 是 Java 中的一个特殊的阻塞队列,它的主要特点是它的容量为0,这意味着 SynchronousQueue不会存储任何元素,需要的朋友可以参考下2023-12-12使用@Autowired可以注入ApplicationContext
这篇文章主要介绍了使用@Autowired可以注入ApplicationContext问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2024-06-06Spring Boot 集成 Swagger2构建 API文档
这篇文章主要介绍了Spring Boot 集成 Swagger2构建 API文档,通过使用 Swagger,我们只需要按照它所给定的一系列规范去定义接口以及接口的相关信息,然后它就能帮我们自动生成各种格式的接口文档,方便前后端开发者进行前后端联调,下文需要的朋友可以参考一下2022-03-03
最新评论