
简介
谁在乎toString s性能?没人!
除非你批量处理大量数据,追求算法的高性能,否则你会使用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队的平均成绩:
公共字符串toString(){ return 我的对象{ 'att1='att1 '''att2='att2 '''att3='att3 '''} 'super . tostring();}//JMH平均性能(每秒操作数)//(最小值,平均值,最大值)=(140772,314,142075,167,143844,717)
Objects.toString
Java 7带来了Objects类和一些静态方法。Objects.toString的优点是它处理空值。如果为空,甚至可以设置默认值。性能略低于前面的代码,但将处理null:
公共字符串toString(){ return 我的对象{ 'att1='objects . tostring(at t1)'''att2='objects . tostring(att 2)'''att3='objects . tostring(att 3)'''} 'super . tostring();}//JMH平均性能(每秒操作数)//(最小值,平均值,最大值)=(138790,233,140791,365,142031,847)
StringBuilder
另一个实现方案是使用StringBuilder。在这里,很难判断哪种技术的性能更好。后三种技术在性能上非常相似。
public String toString(){ final StringBuilder sb=new StringBuilder(我的对象{ );某人追加(att1=').追加(att1)。追加('');某人追加(att2=').追加(att2)。追加('');某人追加(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)。添加(超级uper.toString())。toString();}//JMH平均性能(每秒操作数)//(最小值,平均值,最大值)=(97049,043,110111,808,114878,137)
通用语言3
Commons Lang3有几种生成toString的技术:从生成器到内部检查器。如您所见,内部部分更易于使用,代码行更少,但它会对性能产生严重影响:
公共字符串toString() {返回新的ToStringBuilder(this)。追加(att1 ,att1)。追加(att2 ,att2)。追加(att3 ,att3)。追加(超级uper.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替换串联。
审计唐子红









