简介
AbstractStringBuilder
append 操作
delete 操作
insert 操作
- StringBuilder
- StringBuffer
- StringJoiner
开始自己的一个半年计划, 也就是 java 相关常用类的源码阅读, 通过阅读查漏补缺, 加深基础知识的运用与理解.
简介 #
StringBuilder,StringBuffer 三个类在平时工作中很常用, 因此详细了解下还是很必须的, 由类图可以很清晰的得到其底层都是基于 char[] 数组的存储, 基于数组存储必然会遇到与 List 集合一样的扩容问题, 那么这两个类可以理解为专为字符定制的 List 集合 (实际上与 List 也非常相似). 其中
AbstractStringBuilder
作为 BaseParent 其封装了很多通用的操作, 比如最麻烦的扩容操作, 掌握
StringBuilder,StringBuffer
基本上只要了解
AbstractStringBuilder
就好了.
另外 StringJoiner 是 Java8 所提供的的一个字符串工具类, 从类图来看和其他的类都没关系, 其内部只是对 StringBuilder 的一种封装, 便于更加轻松地连接字符串.
- AbstractStringBuilder#
- AbstractStringBuilder
是提供字符串连接的核心, 其成员变量有 value: char[] 存储容器, count: int 实际字符串大小, int 类型也决定了最大长度不能超过 Integer.MAX_VALUE, 实际上代码中最大长度定义的是
Integer.MAX_VALUE - 8
, 不知道为什么减 8..
append 操作 #
与 List 相同, 基于数组的顺序结构, 在数组改变的时候会有产生容量的问题.
AbstractStringBuilder
在所有的 append 操作前都会先去检查容量, 然后确定容量足够后才往数组添加数据, 容量不足时则新建 oldCount x 2+2 的数组, 把旧数据拷贝进去后继续添加操作.
那么可以得出的结论, 对于能预估大概长度的字符串拼接一次性分配指定容量是一种提高性能的好策略.
1 2 3 4 5 6 7 8 9 10
| private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) { value = Arrays.copyOf(value, newCapacity(minimumCapacity)); } } // newCapacity 逻辑 < br ow="0" oh="0"> int newCapacity = (value.length << 1) + 2; |
delete 操作 #
删除操作与添加类似, 同样需要改变 value 数组, 那么就涉及到数组的元素移动. 主要是由 System.arraycopy 来进行操作, 对于大数组来说删除前面的元素就需要移动后面全部的内容.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public AbstractStringBuilder delete(int start, int end) { if (start < 0) throw new StringIndexOutOfBoundsException(start); if (end > count) end = count; if (start > end) throw new StringIndexOutOfBoundsException(); int len = end - start; if (len > 0) { // 把 end 之后的元素都向前移动 len 位. System.arraycopy(value, start+len, value, start, count-end); count -= len; } return this; } |
insert 操作 #
insert 需要涉及一次移动一次拷贝, 先把移动元素空出位置给要 insert 的字符, 然后再把字符填充进去.
1 2 3 4 5 6 7 8 9 10 11 12
| public AbstractStringBuilder insert(int offset, char[] str) { if ((offset < 0) || (offset > length())) throw new StringIndexOutOfBoundsException(offset); int len = str.length; ensureCapacityInternal(count + len); // 移动空出字符 < br ow="0" oh="0"> System.arraycopy(value, offset, value, offset + len, count - offset); // 填充 < br ow="0" oh="0"> System.arraycopy(str, 0, value, offset, len); count += len; return this; } |
StringBuilder#
由于抽象类不能实例化, 因此其作为
AbstractStringBuilder
的实现类提供日常使用, 内部基本没有自己的逻辑, 绝大部分方法只是调用 super() 的方法委托.
另外在 Java 中字符串拼接绝大多数使用的都是 StringBuilder, 比如下面代码
1 2 3 4 5
| @Test public void test() { String str = "王二"; System.out.println("张三"+"李四" + str); } |
使用 IDEA 插件 ASM Bytecode Outline 反编译之后, 对于 "张三"+" 李四这样的操作直接合并, 对于变量则使用 StringBuilder 进行连接.
1 2 3 4 5
| @Test public void test() { String str = "\u738b\u4e8c"; System.out.println(new StringBuilder().append("\u5f20\u4e09\u674e\u56db").append(str).toString()); } |
那么在循环中使用字符串拼接就可能造成性能问题, 如下代码
1 2 3 4 5 6
| String result = ""; for (int i = 0; i < 100; i++) { // 每次都会创建 StringBuilder 对象, 然后赋值给 result result += i; } |
编译之后的代码每次都会创建 StringBuilder 对象, 可想性能多浪费.
1 2 3 4
| String result = ""; for (int i = 0; i < 100; ++i) { result = new StringBuilder().append((String)result).append((int)i).toString(); } |
StringBuffer#
StringBuffer 操作与 StringBuilder 很类似, 其方法使用 synchronized 修饰, 使其成为一个原子性操作从而保证了线程安全.
StringJoiner#
StringJoiner 是 JDK8 所提供的字符串拼接函数, 直接使用 StringBuilder 拼接也是可以的, 只是有点复杂, 比如下面类似的代码应该不少人写过, 代码并没什么问题, 只是有点小麻烦那么 StringJoiner 实际上就帮助我们解决了这一点的麻烦.
1 2 3 4 5 6 7 8 9 10 11
| List StringBuilder builder = null; for (int i = 0; i < strs.size(); i++) { if (i == 0) { builder = new StringBuilder("start").append(strs.get(i)); } else { |
改写成 StringJoiner 后就比较简洁了.
1 2 3 4 5 6
| List StringJoiner joiner = new StringJoiner(",","start","end"); for (String str : strs) { joiner.add(str); } Assert.assertEquals("start 张三, 李四, 王二 end", joiner.toString()); |
配合 Stream 使用更佳, 这里只是示例, 单步操作并不是很建议使用 Stream,Stream 执行前需要构造自己的执行链, 然后再在一次 for 循环中执行, 其流程也是挺复杂的, 详情可以看我之前 Stream 分析的文章, 相对一次操作感觉性价比不是很高, 还是一个 foreach 循环来的性价比最高.
1 2
| Assert.assertEquals("start 张三, 李四, 王二 end", strs.stream().collect(Collectors.joining(",","start","end"))); |
来源: https://juejin.im/entry/5aa12c06f265da237c687b20