Java CLI 应用程序性能优化:使用 StringBuilder

3

总体:
我正在编写一个套接字客户端,从某个服务器端(远程服务器)一直接收“市场”数据/报价(永不停歇的循环)。
我将数据分成块,以便使用。
每个块包含约200个字符,并需要转换为数组。
在将块分割后,它被解析为列表(这里没有问题)。

问题:
运行10分钟后,CPU使用率达到40%。
我已经成功地找出了问题所在。
每个块都需要转换为json。
因此,现在给出实际执行有问题的代码。
此代码每300-400毫秒执行一次。
跳过此代码将使整个系统的CPU使用率保持在1%-2%。

注意:
我已阅读此线程,但我没有看到任何解决方案。
在循环中重用StringBuilder是否更好?

代码:

private static StringBuffer jsonVal = new StringBuffer();

    public static String toJson(List<QuotesData> quotesData) {
        // Empty variable
        jsonVal.delete(0, jsonVal.length());
        jsonVal.append("{");
        synchronized (quotesData) {
            for (QuotesData quote : quotesData) {

                jsonVal.append("\"").append(quote.getSymbol()).append("\":[{");
                jsonVal.append("\"ask\":\"").append(quote.getAsk()).append(
                        "\",");
                jsonVal.append("\"bid\":\"").append(quote.getBid()).append(
                        "\",");
                jsonVal.append("\"time\":\"").append(quote.getDateTime())
                        .append("\"}],");

            }
            jsonVal.append("}");
            String returnString = jsonVal.toString();
            return returnString.toString().replace("}],}", "}]}");
        }
    }

你使用StringBuffer而不是StringBuilder(标题相反)的原因是什么?你在这个级别进行同步操作吗? - leonbloy
4个回答

2

首先,我建议使用JProfiler或JConsole,它们都包含在JDK6中,以精确定位性能问题所在。

如果不知道CPU使用情况,我会避免使用“synchronized”。我怀疑“append”不是问题所在。还要清理掉局部静态变量“jsonVal”。

public static String toJson(final List<QuotesData> quotesData) {
    final List<QuotesData> theData = new ArrayList<QuotesData>(quotesData);
    StringBuffer jsonVal = new StringBuffer();
    jsonVal.append("{");
    for (QuotesData quote : quotesData) {
        jsonVal.append("\"").append(quote.getSymbol()).append("\":[{");
        jsonVal.append("\"ask\":\"").append(quote.getAsk()).append(
                "\",");
        jsonVal.append("\"bid\":\"").append(quote.getBid()).append(
                "\",");
        jsonVal.append("\"time\":\"").append(quote.getDateTime())
               .append("\"}],");

    }
    jsonVal.append("}");
    String returnString = jsonVal.toString();
    return returnString.toString().replace("}],}", "}]}");
}

考虑使用像Gson这样的JSON库。代码变得更简单。如果需要,您可以调整输出:
private static final Gson gson = new Gson();
public static String toJson(final List<QuotesData> quotesData) {
    return gson.toJson(new ArrayList<QuoteData>(quotesData));
}

谢谢,List<QuotesData> 不可克隆。 - fatnjazzy

0

好的,看起来这是一个经典的过度优化案例。 对象创建并不那么昂贵,你不需要重写同样的字符串缓冲区,特别是如果每300-400毫秒调用一次。

我将尝试解决每种可能的情况:

指数增长

上述代码被分配给一个新线程,每300毫秒执行一次,但列表很大,需要超过300毫秒才能序列化。如果是这种情况,你基本上会耗尽资源,应用程序崩溃只是时间问题。 如果是这种情况,你应该看到CPU不断上升。 解决方案如下:

  1. 限制可以同时运行的线程数量,以避免杀死应用程序
  2. 同时构建json对象并合并结果,使构建单个json所需的时间少于300ms。

加速 好的,列表无法克隆,我假设这意味着它实际上不是一个列表,而是作为列表接口实现的某种队列。因此,在保持同步的情况下,我会这样做:

public static final int JSON_LENGTH = 250; //you probably know this

public static String toJson(final List<QuotesData> quotesData) {
    jsonVal = new StringBuilder(JSON_LENGTH * quotesData.size());
    jsonVal.append("{");
    synchronized (quotesData) {
        for (QuotesData quote : quotesData) {

            jsonVal.append("\"").append(quote.getSymbol()).append("\":[{")
            .append("\"ask\":\"").append(quote.getAsk()).append("\",")
            .append("\"bid\":\"").append(quote.getBid()).append("\",")
            .append("\"time\":\"").append(quote.getDateTime()).append("\"}],");

        }
        // much much faster than replace
        jsonVal.setCharAt(jsonVal.length()-1, '}');
        return jsonVal.toString();
    }
}

大部分的更改都是表面上的,我相信JIT已经对它们进行了优化。唯一的区别是我会使用StringBuilder并每次创建一个新的,而不是使用.replace()。 但为了进一步强调我的观点,除非你符合第一个描述(指数增长),否则我怀疑问题不在这里。我会首先查看您的列表实现。

Asaf,谢谢...似乎没有什么帮助。 我甚至尝试在线程池中执行它... 有趣的是,我在PHP中有相同的代码,它可以更快地长时间运行,而且没有CPU问题... 无论如何, 谢谢。 - fatnjazzy

0

几个建议:

  • 对代码进行分析,它应该会显示热点。
  • 使用 StringBuilder 而不是 StringBufferStringBuffer 是同步的,StringBuilder 不是。
  • synchronized 语句真的需要吗?如果不需要,请尝试将其删除。
  • toString() 在返回语句中不需要。您可以将其删除。
  • 修改代码,使您不需要在最后使用 replace() 方法,如果 returnString 很长,这可能会很昂贵。
  • 尝试在循环之前创建一个新的 StringBuffer 对象,而不是清除旧对象。
  • 尝试返回字符串的 interned 值,即 return returnString.intern()

是的,这是一个线程安全的区域...但我还是尝试了一下...没有任何改变。 - fatnjazzy

0

我猜StringBuilder一直在调整大小。有多少quotesData?我建议你在for循环之前创建一个具有大小的StringBuilder:

StringBuffer jsonVal = new StringBuffer(quotesData.size()*200); //the 200 is on top of my head. Do a few loop to see what is the average length of a QuotesData.

顺便问一下,你考虑过使用 StringBuilder 吗?它与 StringBuffer 相同,但没有线程安全的开销(StringBuffer 是同步的,而 StringBuilder 不是)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接