我正在尝试创建一个Android应用程序,它可以绘制用户输入的简单数学函数(基本上是图形计算器)。 每次onDraw调用都需要每秒进行数百次算术运算(在屏幕上绘制以生成图形)。当我的代码评估表达式时,程序会大大减慢,当内置方法评估表达式时,应用程序运行时没有问题。
根据'LogCat',垃圾收集大约每秒发生12次,每次暂停应用程序大约15毫秒,每秒造成几百毫秒的冻结。我认为这是问题所在。
这是我的评估函数的简化版本。要评估的表达式名为“postfixEquation”,String ArrayList“list”在进程结束时保留最终答案。还有两个名为“digits”和“operators”的String数组,它们存储了能够使用的数字和符号:
String evaluate(String[] postfixEquation) {
list.clear();
for (int i = 0; i < postfixEquation.length; i++) {
symbol = postfixEquation[i];
// If the first character of our symbol is a digit, our symbol is a numeral
if (Arrays.asList(digits).contains(Character.toString(symbol.charAt(0)))) {
list.add(symbol);
} else if (Arrays.asList(operators).contains(symbol)) {
// There must be at least 2 numerals to operate on
if (list.size() < 2) {
return "Error, Incorrect operator usage.";
}
// Operates on the top two numerals of the list, then removes them
// Adds the answer of the operation to the list
firstItem = Double.parseDouble(list.get(list.size() - 1));
secondItem = Double.parseDouble(list.get(list.size() - 2));
list.remove(list.size() - 1);
list.remove(list.size() - 1);
if (symbol.equals(operators[0])){
list.add( Double.toString(secondItem - firstItem) );
} else if (symbol.equals(operators[1])) {
list.add( Double.toString(secondItem + firstItem) );
} else if (symbol.equals(operators[2])) {
list.add( Double.toString(secondItem * firstItem) );
} else if (symbol.equals(operators[3])) {
if (firstItem != 0) {
list.add( Double.toString(secondItem / firstItem) );
} else {
return "Error, Dividing by 0 is undefined.";
}
} else {
return "Error, Unknown symbol '" + symbol + "'.";
}
}
}
// The list should contain a single item, the final answer
if (list.size() != 1) {
return "Error, " + list has " + list.size() + " items left instead of 1.";
}
// All is fine, return the final answer
return list.get(0);
}
操作中使用的数字都是字符串,因为我不确定是否可以在一个数组中保存多个类型(即字符串和双字符),因此猖獗的“Double.parseDouble”和“Double.toString”调用。
我如何减少此处发生的垃圾收集量?
如果有任何帮助,我一直在使用这些步骤来评估我的后缀表达式:http://scriptasylum.com/tutorials/infix_postfix/algorithms/postfix-evaluation/index.htm。 几周和几周我都无法解决这个问题。任何帮助,将不胜感激。谢谢。
答案 0 :(得分:2)
Java中紧密循环的规则是不分配任何内容。你看到如此频繁的GC收集这一事实证明了这一点。
您似乎正在使用Double
进行计算,然后转换为String
。不要这样做,这对于性能来说太糟糕了,因为你创造了大量的字符串然后将它们抛出(加上你在字符串之间来回转换并且经常翻倍)。只需维护一个ArrayDeque<Double>
并将其用作堆栈 - 这也可以使您免于执行可能也会破坏性能的阵列调整大小。
预编译输入方程式。将所有输入操作转换为enum
个实例 - 它们比较快(只需要switch
语句),甚至可以使用更少的内存。如果需要处理双精度数,请使用通用Object
容器和instanceof
,或者包含操作enum
和double
的容器类。预编译使您无需在紧密循环中进行昂贵的测试。
如果你做这些事情,你的循环应该正确地飞行。
答案 1 :(得分:0)
可能你的列表操作是这个问题的根源。列表内部具有数组,根据列表中的数据量扩展/缩小数组。所以做大量的随机添加和删除将需要大量垃圾收集。
避免这种情况的解决方案是针对您的问题使用正确的List
实现,在开头为列表分配足够的空间,以避免调整内部数组的大小并标记未使用的元素而不是删除它们
冻结症状是因为您在UIThread进行了计算。如果您不希望应用冻结,可能需要检查AsyncTask
以在单独的线程上进行计算。
PS:看起来你在那里做了一些无用的操作......为什么parseDouble()
secondItem
?
答案 2 :(得分:0)
您的UI线程中没有发生15ms的暂停,所以它们不应该影响性能。如果您的UI在执行方法时暂停,请考虑在另一个线程(使用AsyncTask)上运行它
要减少垃圾回收,您需要减少循环内分配的内存量。
我建议看看:
答案 3 :(得分:0)
但是,你使用了很多字符串。虽然情况可能并非如此,但它始终是您可以查看的内容之一,因为Java使用String执行时髦的东西。如果你在输出时必须将字符串转换为double,那么就会有相当多的开销。
您需要将数据存储为String吗? (请注意,答案实际上可能是肯定的)大量使用临时字符串实际上会导致垃圾收集器经常被解雇。
小心过早优化。分析器和逐行运行功能可以提供帮助