谁在关心toString的性能?没有人!除非当你有大量的数据在批量处理,使用toString产生了许多日志。然后,你去调查为何如此之慢,才意识到大部分的toString方法使用的是introspection,它其实是可以被优化的。


不过,首先让我们一起看看Javadoc回忆下Object.toString应当做什么:“返回该对象的字符串表示,该结果必须简明但表述详实易懂。建议所有子类重写该方法”。这里最有趣的就是“简明”和“详实”。我们所钟爱的IDE们常常为我们生成equals/hashcode/toString这些方法,且我们通常不再去管它们。此外,这些IDE们提供了许多方式来生成我们自己的toString:字符串连接(使用+号)、StringBuffer、StringBuilder、ToStringBuilder(Commons Lang 3)、 ReflectionToStringBuilder (Commons Lang 3)、Guava或者Objects.toString……该选哪一个?


如果你想知道哪种toString的实现方式会更高效,不要去猜测,而是去测试!这时你需要用到JMH。我曾在博客上写过有关它的文章,所以这里不再细谈JMH如何工作的细节。


在该基准测试中,我创建了一个复杂的对象图(使用继承、集合等等),而且我使用到了由IDE生成的所有不同toString的实现方式,来看看哪一种性能更好。就一条经验法则:简洁。无论你使用哪种技术(如下),为一些属性或者所有属性(包括继承、依赖或者集合)生成toSting,对性能会有巨大的影响。


用 + 连接字符串


让我们先从最高效的方法开始:用 + 连接字符串。曾经这种被认为是邪恶的使用方式(“不要用 + 连接字符串!!!”),已变得很酷且高效!如今JVM编译器(大部分时候)会把 + 编译成一个string builder。所以,不用犹豫,用它就是了。唯一的缺点是null值不会被处理,你需要自己来处理它。


看看下面注解中使用JMH统计出来的平均性能。


public String toString() {

    return "MyObject{" +

            "att1='" + att1 + ''' +

            ", att2='" + att2 + ''' +

            ", att3='" + att3 + ''' +

            "} " + super.toString();

}

 

// Average performance with JMH (ops/s)

// (min, avg, max) = (140772,314, 142075,167, 143844,717)

// 使用JMH测出来的平均性能

// (最小, 平均, 最大) = (140772,314, 142075,167, 143844,717)


用Objects.toString连接字符串


Java SE 7带来了Objects类和它的一些静态方法。Objects.toString的优点是它可以处理null值,甚至可以给null设置默认值。其性能与上一个相比略低,但是null值可以被处理:


public String toString() {

    return "MyObject{" +

            "att1='" + Objects.toString(att1) + ''' +

            ", att2='" + Objects.toString(att2) + ''' +

            ", att3='" + Objects.toString(att3) + ''' +

            "} " + super.toString();

}

 

// Average performance with JMH (ops/s)

// (min, avg, max) = (138790,233, 140791,365, 142031,847)

// 使用JMH测出来的平均性能

// (最小, 平均, 最大) = (138790,233, 140791,365, 142031,847)


StringBuilder


另一种技术是使用StringBuilder。很难讲清哪一种技术性能更好。如我前面所说,我已经使用了复杂的对象图(att1、 att2和att3变量的命名是为了可读性),JMH给出了或多或少相同的结果。后面这三种技术在性能方面非常接近。


public String toString() {

    final StringBuilder sb = new StringBuilder("MyObject{");

    sb.append("att1='").append(att1).append(''');

    sb.append(", att2='").append(att2).append(''');

    sb.append(", att3='").append(att3).append(''');

    sb.append(super.toString());

    return sb.toString();

}

 

// Average performance with JMH (ops/s)

// (min, avg, max) = (96073,645, 141463,438, 146205,910)

// 使用JMH测出来的平均性能

// (最小, 平均, 最大) = (96073,645, 141463,438, 146205,910)


Guava


Guava有一些helper类:其中一个可以帮助你生成toString。这比纯JDK API性能要差一点,但是它可以提供给你一些额外的服务(我这里指的Guava):


public String toString() {

    return Objects.toStringHelper(this)

    .add("att1", att1)

    .add("att2", att2)

    .add("att3", att3)

    .add("super", super.toString()).toString();

}

 

// Average performance with JMH (ops/s)

// (min, avg, max) = (97049,043, 110111,808, 114878,137)

// 使用JMH测出来的平均性能

// (最小, 平均, 最大) = (97049,043, 110111,808, 114878,137)


Commons Lang3


Commons Lang3有一些技术来生成toString:从builder到 introspector。如同你猜测到的,introspection更容易使用,代码量更少,但是性能比较糟糕:


public String toString() {

    return new ToStringBuilder(this)

    .append("att1", att1)

    .append("att2", att2)

    .append("att3", att3)

    .append("super", super.toString()).toString();

}

 

// Average performance with JMH (ops/s)

// (min, avg, max) = ( 73510,509,  75165,552,  76406,370)

// 使用JMH测出来的平均性能

// (最小, 平均, 最大) = ( 73510,509,  75165,552,  76406,370)

 

public String toString() {

    return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);

}

 

// Average performance with JMH (ops/s)

// (min, avg, max) = (31803,224, 34930,630, 35581,488)

// 使用JMH测出来的平均性能

// (最小, 平均, 最大) =(31803,224, 34930,630, 35581,488)

 

public String toString() {

    return ReflectionToStringBuilder.toString(this);

}

 

// Average performance with JMH (ops/s)

// (min, avg, max) = (14172,485, 23204,479, 30754,901)

// 使用JMH测出来的平均性能

// (最小, 平均, 最大) = (14172,485, 23204,479, 30754,901)


总结


如今有了JVM优化,我们可以安全使用+来连接字符串(及使用Objects.toString来处理null)。有了内置到JDK的实用工具类,不需要外部框架来处理null值。因此,与本文中讲述的其它技术相比,开箱即用的JDK拥有更好的性能(如果你有其它的框架/技术,请留下评论我来试试看)。


作为总结,下面是一个从JMH得到的平均性能数据表格(从最高效依次递减)


图片


再说一次,如果你经常调用toString方法,这是很重要的。否则,性能就真不是个事。


更多相关文章

  1. 008. 字符串转换整数 (atoi) | Leetcode题解
  2. Linux性能优化(五)——性能监控工具
  3. Linux性能优化(六)——网络配置工具
  4. Linux性能优化(七)——网络流量监控工具
  5. Linux性能优化(八)——网络测试工具
  6. Linux性能优化(九)——Kernel Bypass
  7. Linux性能优化(十)——CPU性能分析工具
  8. Linux性能优化(十三)——CPU性能测试
  9. Linux性能优化(十一)——CPU性能优化原理

随机推荐

  1. 环境变量在cron中看不到
  2. 将行计数器方法与字数统计方法相结合
  3. java小练习(一个数如果恰好等于它的因子之
  4. Java调用gc机制强制删除文件
  5. 将日期保存到序列化文件
  6. JVM实现跨平台
  7. Java三大框架SSH面试题锦集
  8. 软件大赛题目----(第一个)Java
  9. 数据截断:不正确的datetime值:“用于行1
  10. 如何在JDBC数据源级别限制从Oracle返回的