Java--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的数组,把旧数据拷贝进去后继续添加操作. 那么可以得出的结论,对于能预估大概长度的字符串拼接一次性分配指定容量是一种提高性能的好策略.

   private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

//  newCapacity逻辑
        int newCapacity = (value.length << 1) + 2;

delete操作

删除操作与添加类似,同样需要改变value数组,那么就涉及到数组的元素移动.主要是由System.arraycopy来进行操作,对于大数组来说删除前面的元素就需要移动后面全部的内容.

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的字符,然后再把字符填充进去.

public AbstractStringBuilder insert(int offset, char[] str) {
      if ((offset < 0) || (offset > length()))
          throw new StringIndexOutOfBoundsException(offset);
      int len = str.length;
      ensureCapacityInternal(count + len);
      // 移动空出字符
      System.arraycopy(value, offset, value, offset + len, count - offset);
      // 填充
      System.arraycopy(str, 0, value, offset, len);
      count += len;
      return this;
  }

StringBuilder

由于抽象类不能实例化,因此其作为AbstractStringBuilder的实现类提供日常使用,内部基本没有自己的逻辑,绝大部分方法只是调用super()的方法委托.

另外在Java中字符串拼接绝大多数使用的都是StringBuilder,比如下面代码

@Test
public void test() {
  String str = "王二";
  System.out.println("张三"+"李四" + str);
}

使用IDEA插件ASM Bytecode Outline反编译之后,对于"张三"+"李四这样的操作直接合并,对于变量则使用StringBuilder进行连接.

@Test
   public void test() {
       String str = "\u738b\u4e8c";
       System.out.println(new StringBuilder().append("\u5f20\u4e09\u674e\u56db").append(str).toString());
   }

那么在循环中使用字符串拼接就可能造成性能问题,如下代码

String result = "";

for (int i = 0; i < 100; i++) {
// 每次都会创建StringBuilder对象,然后赋值给result    
  result += i;
}

编译之后的代码每次都会创建StringBuilder对象,可想性能多浪费.

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实际上就帮助我们解决了这一点的麻烦.

List<String> strs = Lists.newArrayList("张三","李四","王二");
   StringBuilder builder = null;
   for (int i = 0; i < strs.size(); i++) {
     if (i == 0) {
       builder = new StringBuilder("start").append(strs.get(i));
     } else {
       builder.append(",").append(strs.get(i));
     }
   }
   builder.append("end");
   Assert.assertEquals("start张三,李四,王二end", builder.toString());

改写成StringJoiner后就比较简洁了.

List<String> strs = Lists.newArrayList("张三","李四","王二");
  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循环来的性价比最高.

Assert.assertEquals("start张三,李四,王二end",
       strs.stream().collect(Collectors.joining(",","start","end")));

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏撸码那些事

编码最佳实践——里氏替换原则

Liskov替换原则(Liskov Substitution Principle)是一组用于创建继承层次结构的指导原则。按照Liskov替换原则创建的继承层次结...

1072
来自专栏MyBlog

Effective.Java 读书笔记(5)复用对象

通常来说我们每次重复使用一个对象是比重新创建一个功能上相等的对象更为合适的,复用可以更快并且更加优雅,当一个对象是不变的(Immutable)时候可以被经常重用

1002
来自专栏java思维导图

你真的懂Java中的String、StringBuilder和StringBuffer吗?

相信String这个类是Java中使用得最频繁的类之一,并且又是各大公司面试喜欢问到的地方,今天就来和大家一起学习一下String、StringBui...

1322
来自专栏Kotlin源码阅读

kotlin源码阅读——函数式编程

我主要写Kotlin源码阅读,函数式编程的基本概念,概念大家可以在网上做一些了解,这里推荐一下百度百科的定义,函数式编程概念,蛮清晰的。

3105
来自专栏技术/开源

30分钟?不需要,轻松读懂IL

先说说学IL有什么用,有人可能觉得这玩意平常写代码又用不上,学了有个卵用。到底有没有卵用呢,暂且也不说什么学了可以看看一些语法糖的实现,或对.net理解更深一点...

1967
来自专栏owent

C++ 新特性学习(七) — 右值引用

C++在效率上有个硬伤。我们知道C#和Java对于类传递都是以引用的方式,而C++默认都是传值。在传值过程中就经常会进行复制构造,这完全没必要而且浪费CPU,为...

931
来自专栏微信公众号:Java团长

探秘Java中的String、StringBuilder以及StringBuffer

  相信String这个类是Java中使用得最频繁的类之一,并且又是各大公司面试喜欢问到的地方,今天就来和大家一起学习一下String、StringBuilde...

792
来自专栏bboysoul

1461: C语言实验题――求平均值

描述:求n个数的平均数。 输入:输入数据有2行,第一行为n,第二行是n个数。 输出:输出n个数中的平均数,结果保留小数点2位。 样例输入:5-1 2.1 ...

1552
来自专栏机器学习算法与Python学习

Python: 早点知道这些就不会这样了

现在在Python 2的代码中都用import from future来导入Python 3的输出和除法。现在用到的几乎所有库都支持Python 3,因此会很快...

2714
来自专栏linux驱动个人学习

Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析【转】

Android系统的运行时库层代码是用C++来编写的,用C++ 来写代码最容易出错的地方就是指针了,一旦使用不当,轻则造成内存泄漏,重则造成系统崩溃。不过系统为...

832

扫码关注云+社区

领取腾讯云代金券