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源码分析的更多相关文章
- ThreadLocal源码及相关问题分析
前言 在高并发的环境下,当我们使用一个公共的变量时如果不加锁会出现并发问题,例如SimpleDateFormat,但是加锁的话会影响性能,对于这种情况我们可以使用ThreadLocal.ThreadL ...
- JDK数组阻塞队列源码深入剖析
JDK数组阻塞队列源码深入剖析 前言 在前面一篇文章从零开始自己动手写阻塞队列当中我们仔细介绍了阻塞队列提供给我们的功能,以及他的实现原理,并且基于谈到的内容我们自己实现了一个低配版的数组阻塞队列.在 ...
- 二维码zxing源码分析(五)精简代码
由于工作的需要,我并不是需要二维码扫描的所有的功能,我只是需要扫一扫,并显示出来图片和url就行,于是我们就要精简代码了,源码已经分析完了,精简起来就方便多了,源码分析请看 二维码zxing源码分析( ...
- ArrayList源码和多线程安全问题分析
1.ArrayList源码和多线程安全问题分析 在分析ArrayList线程安全问题之前,我们线对此类的源码进行分析,找出可能出现线程安全问题的地方,然后代码进行验证和分析. 1.1 数据结构 Arr ...
- Java集合框架——jdk 1.8 ArrayList 源码解析
前言:作为菜鸟,需要经常回头巩固一下基础知识,今天看看 jdk 1.8 的源码,这里记录 ArrayList 的实现. 一.简介 ArrayList 是有序的集合: 底层采用数组实现对数据的增删查改: ...
- Okhttp3源码解析(3)-Call分析(整体流程)
### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...
- Okhttp3源码解析(2)-Request分析
### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...
- Spring mvc之源码 handlerMapping和handlerAdapter分析
Spring mvc之源码 handlerMapping和handlerAdapter分析 本篇并不是具体分析Spring mvc,所以好多细节都是一笔带过,主要是带大家梳理一下整个Spring mv ...
- HashMap的源码学习以及性能分析
HashMap的源码学习以及性能分析 一).Map接口的实现类 HashTable.HashMap.LinkedHashMap.TreeMap 二).HashMap和HashTable的区别 1).H ...
- 物联网防火墙himqtt源码之MQTT协议分析
物联网防火墙himqtt源码之MQTT协议分析 himqtt是首款完整源码的高性能MQTT物联网防火墙 - MQTT Application FireWall,C语言编写,采用epoll模式支持数十万 ...
随机推荐
- 超声波、毫米波、ToF激光雷达——在低功耗场景的应用选型
前言: 目前主要的测距方式有:光学测距,超声波和微波雷达测距. 光学测距又可以分为:双目,结构光,ToF.微波雷达,在消费类产品中,常见的是波长在毫米级别的毫米波雷达.超声波应用比较多的是在车载倒车雷 ...
- 【Python基础】数据类型与类型转换
五种基本数据类型 在 Python 中,基本数据类型是指不可变对象的数据类型.以下是 Python 中的基本数据类型: 整数类型(int):表示整数,例如 1.2.3 等等. 浮点数类型(float) ...
- 【HTML-CSS】div中加入icon后input标签占用不满问题
做登录表单时遇到了一个宽度控制不好的问题,放入图标后,input框总是无法正确的填满剩余空间(尺寸过大/过小) 原因是input元素和父元素div宽度都写死的问题 把父元素的高度删除,宽度改成max- ...
- IDEA中GIT提交后,发现提交有误想修改提交
问题描述:在IDEA开发工具中,使用GIT提交本地后,在push时发现有问题,想要修改提交的内容. 步骤 一:打开version control,点击log 二:找到提交记录,右键点击Undo com ...
- ACM中的java的使用;
java大法好,退C保平......开玩笑的: 1.头文件: import java.math.*; // 包含大数类的包 import java.util.*; // 包含输入头的包 2.程序主体, ...
- 记一次 .NET 某医院门诊软件 卡死分析
一:背景 1. 讲故事 前几天有位朋友找到我,说他们的软件在客户那边卡死了,让我帮忙看下是怎么回事?我就让朋友在程序卡死的时候通过 任务管理器 抓一个 dump 下来,虽然默认抓的是 wow64 ,不 ...
- 基于go语言的声明式流式ETL,高性能和弹性流处理器
简要介绍Benthos,并给出若干示例,指导如何安装和运行. Benthos Benthos 是一个开源的.高性能和弹性的数据流处理器,能够以各种代理模式连接各种源和汇,可以帮助用户在不同的消息流之间 ...
- Django4全栈进阶之路21 项目实战(在线报修):创建App应用和Model模型
创建应用App python manage.py startapp RepairApp 创建模型 在models.py文件中定义一个Repair模型来表示报修单,其中包含以下字段: repair_id ...
- .NET6 + EF Core + MySQL 创建实体和数据库、EFCore 数据迁移
前言 接上期文章<.NET6项目连接数据库方式方法>,有人问了我几个问题,现在就这几个问题,拓展延申一下创建实体类.数据库.把ORM框架和数据迁移都写进去. 安装ORM框架,这里我们采用E ...
- 计算机网络 VRRP和DHCP
目录 一.vrrp概念 二.vrrp工作过程 三.vrrp优先级 四.vrrp实验 五.DHCP概念 六.DHCP工作过程 七.DHCP实验 一.vrrp概念 概念:称虚拟路由器冗余协议,当网关路由器 ...