背景

功能描述:将多个元素使用指定符号前后连接为字符串;eg:1 2 3 4 5 , => 1,2,3,4,5

要点:

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

使用方法:

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

源代码

基础属性

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

构造方法

  1. public StringJoiner(CharSequence delimiter) {
  2. this(delimiter, "", "");
  3. }
  4. public StringJoiner(CharSequence delimiter,
  5. CharSequence prefix,
  6. CharSequence suffix) {
  7. Objects.requireNonNull(prefix, "The prefix must not be null");
  8. Objects.requireNonNull(delimiter, "The delimiter must not be null");
  9. Objects.requireNonNull(suffix, "The suffix must not be null");
  10. // make defensive copies of arguments
  11. this.prefix = prefix.toString();
  12. this.delimiter = delimiter.toString();
  13. this.suffix = suffix.toString();
  14. this.emptyValue = this.prefix + this.suffix;
  15. }

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

值得注意的是:

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

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

添加元素

  1. public StringJoiner add(CharSequence newElement) {
  2. prepareBuilder().append(newElement);
  3. return this;
  4. }
  5. private StringBuilder prepareBuilder() {
  6. if (value != null) {
  7. value.append(delimiter);
  8. } else {
  9. value = new StringBuilder().append(prefix);
  10. }
  11. return value;
  12. }

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

prepareBuilder 内部会先判断 value 是否为空,

若为空则构建StringBuilder对象且追加前缀,如果不为空则追加分隔符;

执行逻辑如下:

  1. joiner.add("1");
  2. ---
  3. prepareBuilder() // value = "start"
  4. .append("1"); // value = "start1"
  5. =========================================================
  6. joiner.add("2");
  7. ---
  8. prepareBuilder() // value = "start1,"
  9. .append("2"); // value = "start1,2"
  10. =========================================================
  11. joiner.add("3");
  12. ---
  13. prepareBuilder() // value = "start1,2,"
  14. .append("3"); // value = "start1,2,3"

拼接结果

  1. public String toString() {
  2. // 没有调用add方法添加元素,直接返回空值
  3. if (value == null) {
  4. return emptyValue;
  5. } else {
  6. // 没有后缀,直接将value.toString()即可
  7. if (suffix.equals("")) {
  8. return value.toString();
  9. } else {
  10. // 追加后缀后返回
  11. int initialLength = value.length();
  12. String result = value.append(suffix).toString();
  13. // reset value to pre-append initialLength
  14. value.setLength(initialLength);
  15. return result;
  16. }
  17. }
  18. }

核心的逻辑的在:

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

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

疑问点

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

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

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

  1. StringJoiner joiner = new StringJoiner(",", "start", "end");
  2. List<String> elements = Arrays.asList("1","2","3","4")
  3. for (String s: elements) {
  4. joiner.add(s);
  5. }
  6. joiner.toString(); // "start1,2,3,4end"
  7. joiner.add("5");
  8. joiner.toString(); // "start1,2,3,4end,5end"

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

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

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

看下StringBuilder中的setLength的实现:

  1. //java.lang.AbstractStringBuilder#setLength
  2. public void setLength(int newLength) {
  3. if (newLength < 0)
  4. throw new StringIndexOutOfBoundsException(newLength);
  5. ensureCapacityInternal(newLength);
  6. if (count < newLength) {
  7. Arrays.fill(value, count, newLength, '\0');
  8. }
  9. // 设置count为填充后缀之前的长度值
  10. count = newLength;
  11. }
  12. // java.lang.StringBuilder#toString
  13. public String toString() {
  14. // 使用value[0,count]位置的字符生成结果
  15. // Create a copy, don't share the array
  16. return new String(value, 0, count);
  17. }

可以看到,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源码分析的更多相关文章

  1. ThreadLocal源码及相关问题分析

    前言 在高并发的环境下,当我们使用一个公共的变量时如果不加锁会出现并发问题,例如SimpleDateFormat,但是加锁的话会影响性能,对于这种情况我们可以使用ThreadLocal.ThreadL ...

  2. JDK数组阻塞队列源码深入剖析

    JDK数组阻塞队列源码深入剖析 前言 在前面一篇文章从零开始自己动手写阻塞队列当中我们仔细介绍了阻塞队列提供给我们的功能,以及他的实现原理,并且基于谈到的内容我们自己实现了一个低配版的数组阻塞队列.在 ...

  3. 二维码zxing源码分析(五)精简代码

    由于工作的需要,我并不是需要二维码扫描的所有的功能,我只是需要扫一扫,并显示出来图片和url就行,于是我们就要精简代码了,源码已经分析完了,精简起来就方便多了,源码分析请看 二维码zxing源码分析( ...

  4. ArrayList源码和多线程安全问题分析

    1.ArrayList源码和多线程安全问题分析 在分析ArrayList线程安全问题之前,我们线对此类的源码进行分析,找出可能出现线程安全问题的地方,然后代码进行验证和分析. 1.1 数据结构 Arr ...

  5. Java集合框架——jdk 1.8 ArrayList 源码解析

    前言:作为菜鸟,需要经常回头巩固一下基础知识,今天看看 jdk 1.8 的源码,这里记录 ArrayList 的实现. 一.简介 ArrayList 是有序的集合: 底层采用数组实现对数据的增删查改: ...

  6. Okhttp3源码解析(3)-Call分析(整体流程)

    ### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...

  7. Okhttp3源码解析(2)-Request分析

    ### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...

  8. Spring mvc之源码 handlerMapping和handlerAdapter分析

    Spring mvc之源码 handlerMapping和handlerAdapter分析 本篇并不是具体分析Spring mvc,所以好多细节都是一笔带过,主要是带大家梳理一下整个Spring mv ...

  9. HashMap的源码学习以及性能分析

    HashMap的源码学习以及性能分析 一).Map接口的实现类 HashTable.HashMap.LinkedHashMap.TreeMap 二).HashMap和HashTable的区别 1).H ...

  10. 物联网防火墙himqtt源码之MQTT协议分析

    物联网防火墙himqtt源码之MQTT协议分析 himqtt是首款完整源码的高性能MQTT物联网防火墙 - MQTT Application FireWall,C语言编写,采用epoll模式支持数十万 ...

随机推荐

  1. vue-cli3构建和发布 实现分环境打包步骤(给不同的环境配置相对应的打包命令)

    https://panjiachen.github.io/vue-element-admin-site/zh/guide/essentials/deploy.html#%E6%9E%84%E5%BB% ...

  2. 2022-11-15:这里有 n 个航班,它们分别从 1 到 n 进行编号。 有一份航班预订表 bookings , 表中第 i 条预订记录 bookings[i] = [firsti, lasti,

    2022-11-15:这里有 n 个航班,它们分别从 1 到 n 进行编号. 有一份航班预订表 bookings , 表中第 i 条预订记录 bookings[i] = [firsti, lasti, ...

  3. 2022-05-15:N个学校之间有单向的网络,每个学校得到一套软件后,可以通过单向网络向周边的学校传输。 问题1:初始至少需要向多少个学校发放软件,使得网络内所有的学校最终都能得到软件; 问题2:至

    2022-05-15:N个学校之间有单向的网络,每个学校得到一套软件后,可以通过单向网络向周边的学校传输. 问题1:初始至少需要向多少个学校发放软件,使得网络内所有的学校最终都能得到软件: 问题2:至 ...

  4. Selenium - 元素操作(3) - 下拉框操作

    Selenium - 元素操作 下拉框才做可以分为两类: select标签的下拉框:使用Select类进行操作: 非select标签的下拉框:一般是 ul,li, div 等标签组成,使用元素定位的方 ...

  5. 巧用OpenSSH进行域内权限维持

    最近在Windows服务器上安装OpenSSH,意外发现了一个很有意思的技巧,可用来做域内权限维持,废话不多说,直接上步骤. 01.利用方式 (1)在已经获得权限的Windows服务器上,使用msie ...

  6. linux 系统安全和应用

    目录 一.系统安全 二.账号安全 三.修改密码生效时间 四.强制下次登录成功时修改密码 五.历史命令 六.终端自动注销 七.wheel组 八.grub菜单密码 一.系统安全 原因:1.系统数据想要保护 ...

  7. Vue 异步通信Axios

    使用Axios实现异步通信需要先导入cdn: <script src="https://unpkg.com/axios@1.4.0/dist/axios.min.js"> ...

  8. 代码随想录算法训练营Day36 贪心算法

    代码随想录算法训练营 代码随想录算法训练营Day36 贪心算法| 435. 无重叠区间 763.划分字母区间 56. 合并区间 435. 无重叠区间 题目链接:435. 无重叠区间 给定一个区间的集合 ...

  9. 1、初认 AS400

    一.AS400 简介 AS/400是一种主机型计算机,是IBM公司开发的.AS/400是IBM的应用服务器产品,针对企业级应用开发.重要应用系统支持进行设计开发.AS/400的系统工作环境中同时支持多 ...

  10. Python网页开发神器fac 0.2.9、fuc 0.1.29新版本更新内容介绍

    fac项目地址:https://github.com/CNFeffery/feffery-antd-components fuc项目地址:https://github.com/CNFeffery/fe ...