
简介
谁在乎托斯特林的表现?没人!
除非你批量处理大量数据,追求算法的高性能,否则你会使用toString进行大量的日常类型转换。然后,你会研究它为什么慢,意识到toString()主要是内部实现的,是可以优化的。
首先,我们来看看Javadoc对Object.toString应该做什么的描述:“返回对象的字符串表示。通常,这个toString方法返回一个将该对象“表示”为文本的字符串。结果要简明易懂,便于人们阅读。建议所有子类都重写此方法。IDE(idea,eclipse)经常为我们生成equals、hashcode和toString方法的覆盖.我们通常这样做。另外,IDE为我们提供了几个生成toString的选项:String cascade(使用符号)、StringBuffer、StringBuilder、ToStringBuilder、ReflectionToStringBuilder、Guava或Objects.toString。
这些实现方案你会选择哪一个?
如果你想知道哪个实现更有效,我们可以用JMH基准来看看效果。
对于这个基准,我创建了类(使用继承、集合等。)并使用了idea生成的所有不同的toString实现,看看哪一个的性能更高。代码应该尽可能简单。无论使用哪种技术(见下文),为部分或全部属性(包括继承、依赖和集合)生成toString都会对性能产生很大影响。
标志
让我们从性能最高的方法开始:有符号字符串连接。很多人告诉我们不要用数字生成字符串,这是不友好的,尤其是在JVM7之前。然而,Java编译器将符号编译成字符串生成器(在大多数情况下),并进行大量优化。所以不要犹豫使用它。但它唯一的缺点是不处理空值,需要你自己做特殊处理。
在下面的结果中,是JMH的平均表现:
public String toString(){ return ' my object { ' ' at t1=' ' at t1 ' ' 'att2='' att2 ' ' 'at T3=' ' at T3 ' ' ' } ' super . toString();}//JMH平均性能(每秒操作数)//(最小值,平均值,最大值)=(140772,314,142075,167,143844,717)
Objects.toString
Java SE 7带来了对象类和一些静态方法。Objects.toString的优点是处理空值,如果是空值,甚至可以设置默认值。性能略低于前面的代码,但它将处理null:
public String toString(){ return ' my object { ' ' at t1=' ' objects . toString(at t1)' ' ' 'att2='' Objects.toString(att2)' ' ' 'at T3=' ' objects . toString(at T3)' ' } ' super . toString();}//JMH平均性能(每秒操作数)//(最小值,平均值,最大值)=(138790,233,140791,365,142031,847)
StringBuilder
另一个实现方案是使用StringBuilder。在这里,很难判断哪种技术的性能更好。后三种技术在性能上非常相似。
public String toString(){ final StringBuilder sb=new StringBuilder(' my object { ');sb.append('att1=' ')。追加(att1)。追加(“”);sb.append('att2=' ')。追加(att2)。追加(“”);sb.append('att3=' ')。追加(att3)。追加(“”);sb . append(super . tostring());return sb . tostring();}//JMH平均性能(每秒操作数)//(最小值,平均值,最大值)=(96073,645,141463,438,146205,910)
番石榴
Guava有几个助手类:其中一个可以帮助你生成toString。它的性能不如纯JDK API,但是番石榴可以为你提供一些额外的服务。
公共字符串toString(){ return objects . toString helper(this)。添加(' att1 'att1)。添加(' att2 'att2)。添加(' att3 'att3)。add('super 'super.toString())。toString();}//JMH平均性能(每秒操作数)//(最小值,平均值,最大值)=(97049,043,110111,808,114878,137)
通用语言3
Commons Lang3有几种生成toString的技术:从生成器到内部检查器。如您所见,它更易于在内部使用,代码行也更少,但它会对性能产生严重影响:
公共字符串toString() {返回新的ToStringBuilder(this)。append('att1 'att1)。append('att2 'att2)。append('att3 'att3)。append('super 'super.toString())。toString();}//JMH(ops/s)//(min,avg,max)=( 73510,509,75165,552,76406,370)公共字符串toString(){ return toString builder . reflection toString(this,ToStringStyle。SHORT _前缀_样式);} //平均性能与JMH (ops/s) //(min,avg,max)=(31803,224,34930,630,35581,488)公共字符串toString(){ return reflectiontostringbuilder . toString(this);}//JMH平均性能(每秒操作数)//(最小值,平均值,最大值)=(14172,485,23204,479,30754,901)
结论
如今,随着JVM的优化,我们可以安全地使用符号连接字符串(并使用Objects.toString处理空值)。使用JDK内置的实用程序类对象,您可以在没有外部框架的情况下处理空值。因此,开箱即用的JDK比本文介绍的任何其他技术都有更好的性能(如果您有任何其他框架/技术,请给我留言,我会尝试一下。欢迎交流)。
总而言之,这是一张表,包含了JMH的平均表现(从最好到最差):
202209222331368601.png
JMH结果
同样,如果您经常调用toString方法,那么所有这些都很重要。如果不是,性能就不是真正的问题。你可以用它,不管它有多方便。
发展
目标号码拼接
打包tostringpublic class Main { public static void Main(String[]args){ int n=1000,iterations=10000长透镜,t0,t1;//字符串生成器:1秒len=0;t0=system . current time millis();for(int j=0;j次迭代;j){ StringBuilder builder=new StringBuilder();for(int I=0;I n;I){ builder . append(I);} len=builder.toString()。长度();} t1=system . current time millis();system . out . println(len ' '(t1-t0));//字符串串联:10秒len=0;t0=system . current time millis();for(int j=0;j次迭代;j){ String RES=' 'for(int I=0;I n;I){ RES=I;} len=res.length()。} t1=system . current time millis();system . out . println(len ' '(t1-t0));} }
注意字符串连接,因为JVM不够智能,无法优化复杂的流。一个简单的循环会极大地影响性能,这也是JDK强调“简单”的原因。您应该避免重复使用toString方法。
的String concat可能与String builder具有相同的性能。
奇怪的是,String concat with与String builder花费的时间几乎相同。
出现这种情况的原因是编译器做了一些优化。编译时,javac用StringBuilder替换串联。
回顾唐子红









