StringBuilder.append长string时出问题。

今天PROD遇到问题了,查询大量数据,然后返回XML, 在StringBuilder.append时发生错误,而且发生错误是在append一个小String时发生的错误。如果内存设置也很大,并且出示capacity的逻辑也比较麻烦,那么就要从设计上下功夫了,比如看看为什么在这一短时间访问量这么大,能不能均衡一下。String在expandCapacity会进行一下currentsize*2 + 2,是否大于要追加的string的长度,如果小于就扩展当前长度+要追加的String的长度。

ArrayList使用currentsize*3/2 + 1和追加的数据长度来比较

java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2882)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:390)
at java.lang.StringBuilder.append(StringBuilder.java:119)

A:

http://www.captaincasademo.com/forum/posts/list/1503.page

Hi, 

today we were forced to dive into the StringBuffer class - after looking into a OutOfMemory problem in our client. 

It's "amazing" that the following code causes an OutOfMemory - event though the virtual machine is started with -Xmx256m: 

Code:

public static void main(String[] args)
     {
         try
         {
             char[] chars = new char[30000000];
             for (int i=0; i<chars.length; i++) chars[i] = 'A';
             String s = new String(chars);
             StringBuffer sb = new StringBuffer();
             System.out.println("Now appending s");
             sb.append(s);
             System.out.println("Now appending Hallo");
             sb.append("Hallo!"); 
             // ----
             System.out.println("Finished!");
         }
         catch (Throwable t)
         {
             t.printStackTrace();
         }
     }
 
 
 OUTPUT:
 Now appending s
 Now appending Hallo
 java.lang.OutOfMemoryError: Java heap space
 	at java.util.Arrays.copyOf(Arrays.java:2882)
 	at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
 	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:390)
 	at java.lang.StringBuffer.append(StringBuffer.java:224)
 	at org.eclnt.client.ztest.TestMem.main(TestMem.java:22)


A String with the length of 30.000.000 characters is added to a stringbuffer - that's it. It's even more astonishing that the error is NOT thrown when appending the "big" string, but when appending "Hallo". 

The reason is the internal processing of StirngBuffer. StringBuffer keeps everyhing in an char-array, and there is a certain method that increases the char-array-size when finding out that the array does not fit anymore. The method increases the size by at least "2 * currentSize + 2". This may make sense for small sizes, but it's completely a wrong decision for big sizes. In our case the appending of "Hello" means that the total array size is increased by 60.000.002 as minimum, so that the minimum size is 90.000.002 afterwards. And now, the previous char array with length of 30.000.000 needs to be copied into the one of 90.000.002 - this means both arrays co-exist for a short duration of time, resulting in 120.000.002 characters being in memory (and of course there is some additional memory blocked by the array object). So the appending of "Hallo" means a lot... - enough to cause out of memory. 

When concatenating long strings, then it is as consequence essential to carefully work with StringBuffers. We eliminated our OutOfMemory problems by using the following utitlity method: 

Code:
public static String concatenateStrings(List<String> items)
     {
         if (items == null)
             return null;
         if (items.size() == 0)
             return "";
         int expectedSize = 0;
         for (String item: items)
             expectedSize += item.length();
         StringBuffer result = new StringBuffer(expectedSize);
         for (String item: items)
             result.append(item);
         return result.toString();
     }


When now changing the test code to: 
Code:
public static void main(String[] args)
     {
         try
         {
             char[] chars = new char[30000000];
             for (int i=0; i<chars.length; i++) chars[i] = 'A';
             String s = new String(chars);
             List<String> buffer = new ArrayList<String>();
             System.out.println("Now appending s");
             buffer.add(s);
             System.out.println("Now appending Hallo");
             buffer.add("Hallo!");
             String all = concatenateStrings(buffer);
             // ----
             System.out.println("Finished!");
         }
         catch (Throwable t)
         {
             t.printStackTrace();
         }


...then no error occurs anymore. 


you can set an inital capacity for StringBuffer i guess. 

Also i would recommend to use StringBuilder instead of StringBuffer (in case you don't need a garanteed synchronisation). 

this test (link) resulted in a 34% better perfomance for StringBuilder. 

http://littletutorials.com/2008/07/16/stringbuffer-vs-stringbuilder-performance-comparison/

Hi, 

you are 100% right...! 
The setting of an internal capacity is by the way the "trick" behind the concatenateStrings-method int the text above. Before appending things in the StringBuffer, its size is calculated (variable "expectedSize"). 

Regards, Björn 

转载于:https://my.oschina.net/u/138995/blog/215185

  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 扫一扫,分享海报

参与评论
请先登录 后发表评论~
©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值