字符串和垃圾回收

4

我正在使用一个引擎(AndEngine)中的方法:

public final void setText(String pString){...} 

我的应用程序每1秒钟从静态整型变量更新得分。

mScoreText.setText(""+PlayerSystem.mScore);

问题在于每秒钟都会创建一个新的字符串对象,一分钟后我需要收集59个字符串对象以及额外的AbstractStringBuilders和init...。我在andengine论坛上找到了部分解决方案,如下所示:
private static StringBuilder mScoreValue = new StringBuilder("000000");

private static final char[] DIGITS = {'0','1','2','3','4','5','6','7','8','9'};

mScoreValue.setCharAt(0, DIGITS[(PlayerSystem.mScore% 1000000) / 100000]);
mScoreValue.setCharAt(1, DIGITS[(PlayerSystem.mScore% 100000) / 10000]);
mScoreValue.setCharAt(2, DIGITS[(PlayerSystem.mScore% 10000) / 1000]);
mScoreValue.setCharAt(3, DIGITS[(PlayerSystem.mScore% 1000) / 100]);
mScoreValue.setCharAt(4, DIGITS[(PlayerSystem.mScore% 100) / 10]);
mScoreValue.setCharAt(5, DIGITS[(PlayerSystem.mScore% 10)]);
mScoreText.setText(mScoreValue.toString());

但主要问题仍然存在,每次调用.toString()都会返回一个新对象。有没有办法解决这个问题?

1
你真的担心每秒一个字符串吗?对我来说似乎不算多,特别是因为我预计每次点击或其他事件也会创建一个新的(事件)对象。 - Roland Illig
2
是的,别在意了。垃圾收集器足够快,担心每秒钟一个分配完全是不成比例的。这种毫无意义的琐事正是计算机最擅长的——让它们去做,把你的脑力用在更有价值的事情上。 - hmakholm left over Monica
使用这段代码会导致严重的过早优化问题。我认为最后所有的模数运算、除法运算、数组获取和在 StringBuilder 中设置字符所需的处理量都比一个 String 创建及其相关垃圾回收要多得多。而且就像其他人指出的那样,最后你还是要通过 toString() 来创建一个 String。如果你在处理中受到了分配每分钟 60 个小对象的限制,那么 Java 不是你想用的语言。C 或者汇编将更接近标记。 - G_H
使用String.valueOf(PlayerSystem.mScore)不是更好吗?因为它跳过了StringBuilder的分配。 - alexanderblom
3个回答

4

听起来使用StringBuilder是一个不错的选择:

http://developer.android.com/reference/java/lang/StringBuilder.html

或者StringBuffer:

http://developer.android.com/reference/java/lang/StringBuffer.html

推理是:

StringBuffer 用于存储将被更改的字符字符串(String 对象无法更改)。它会根据需要自动扩展。相关类:String,CharSequence。

Java 5 中添加了 StringBuilder。除了不是同步的外,它在所有方面都与 StringBuffer 相同,这意味着如果多个线程同时访问它,可能会出问题。对于单线程程序(最常见的情况),避免同步开销使 StringBuilder 稍微快一点。

编辑: 你必须小心使用选择的 SB 类。原因是(.Net 中也是如此),如果您有这样的用法,

StringBuilder sb = new StringBuilder(score.ToString() + "hello, world!");

你仍然有2个字符串连接操作,可能会实际生成3个字符串,一个是用于score.ToString(),一个是将文字"hello, world!"转换为字符串,还有一个包含两个字符串连接在一起的字符串。 为了获得最佳结果,你需要使用SB的Append/insert/replace方法。

1
它们比""+int更快,但最终你得到相同数量的对象。 - SJuan76
1
他仍然需要调用toString方法,这仍然会创建一个新的字符串对象,因此问题仍然存在。 - Daniel
StringBuilder 甚至不会比 ""+int 更快,因为创建 StringBuilderStringBuffer 类的开销。它们真正有用的只是在循环中修改字符串多次。 - Daniel
这个问题是关于Android开发的。 - Russ Clarke
@Russ C 当然,这就是为什么我明确提到了javac,因为我还没有反汇编android的源代码以确保。但是,由于很长时间没有JIT,Android编译器通常会进行更多的优化,而且您无论如何都必须使用一些函数调用来实现string + string,因此使用stringbuilder并不是一个额外的步骤 - 所以它肯定是相同的。 - Voo
显示剩余2条评论

3
据我所知,无法规避 Strings 是不可变的事实。如果你的方法接受一个 String 参数,每次调用该方法都会创建一个新的字符串。

可以使用反射来提高性能,但是会有更大的性能损失。而且每秒钟创建一个小对象?可笑得无趣。 - Voo
1
我同意这并不是一个大问题,但我认为有责任心的程序员应该意识到这些问题,而不仅仅是说“没关系,编译器会处理好的”。这表明了对能力和对工艺的热爱。 - Russ Clarke
@Russ C 我不同意。任何优化的最重要的事情是知道你能赢得多少。 <插入 Knuth 的引用>。我会接受每一个赌注,如果他对代码进行分析,他甚至都不会注意到它。这在计时方面根本不会有任何影响(相信我,至少对于 javac,我知道它将为字符串连接生成什么代码 - 如果没有循环或函数调用涉及,那么忘记它是一个好规则)。 - Voo
1
让我们同意有不同的看法;但是我不能诚实地接受你认为意识到代码影响的重要性并不重要。然而,我们可能应该将这场辩论转移到 programmers.stackexchange。 - Russ Clarke
我在那里不活跃,但毕竟是一个有趣的话题(会留意一下线程的);)。 - Voo

3

首先,两分钟内处理120个对象不应该让你担心,除非它们非常大。

其次,String类保存了所有创建的字符串。所以,如果您执行以下操作:

 String a = new String("Nabucodonosor King of Babilonia");
 String b = new String("Nabucodonosor King of Babilonia");

然后,巴比伦国王尼布甲尼撒只在内存中存储了一次(但有两个指向它的String对象)。请查看String#intern()获取详细信息。

最后,正如Daniel所指出的那样,由于字符串是不可变的,因此无法使用字符串进行解决。您可以做一些技巧(检查新值和旧值是否相同,并仅在它们不同时创建字符串),但我怀疑它们是否能够弥补增加的复杂性。


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