我试图找到我在野外看到的现实生活中的例子(也许略有改变,但基本问题都非常真实)。我还尝试围绕相同的场景将它们群集起来,以便您可以轻松构建会话。
场景:您有一个耗时的函数,您希望对不同的值执行多次,但相同的值可能会再次弹出(理想情况下,在创建后不久)。一个很好的例子是您需要下载和处理的url-web页面对(对于练习,它应该是模拟的)。
循环:
-
您要检查页面中是否弹出了一组单词中的任何一个。在循环中使用函数,但具有相同的值,伪代码:
for (word : words) {
checkWord(download(url))
}
一个解决方案非常简单,只需在循环之前下载页面即可。其他解决方案如下。
内存泄漏:
- 简单的一个:你也可以用一种缓存来解决你的问题。在最简单的情况下,您可以将结果放到(静态)地图中。但是,如果不阻止它,它的大小将无限增长 - >内存泄漏。
可能的解决方案:使用 LRU 映射。最有可能的是,性能不会降低太多,但内存泄漏应该会消失。
- 更棘手的一个:假设你使用一个实现以前的缓存,其中键是URL(不是字符串,见后面),值是包含URL,下载页面和其他内容的类的实例。你可能会认为它应该没问题,但实际上并非如此:由于值(不是弱引用)具有对密钥(URL)的引用,因此密钥将永远没有资格清理 - >个不错的内存泄漏。
解决方案:从值中删除 URL。WeakHashMap
- 与以前相同,但url是暂存字符串(“如果我们碰巧再次使用相同的字符串,则节省一些内存”),值不是指此。我没有尝试过,但在我看来,它也会导致泄漏,因为实习的字符串不能被GC-ed。
解决方案:不要实习,这也会导致你一定不要跳过的建议:不要过早地优化,因为它是所有邪恶的根源。
对象创建和字符串:
- 假设您只想显示页面的文本(~删除html标签)。编写一个逐行执行该操作的函数,并将其追加到不断增长的结果中。起初,结果应该是一个字符串,因此追加将花费大量时间和对象分配。您可以从性能角度(为什么追加速度如此之慢)和对象创建角度(为什么我们创建了这么多字符串,StringBuffers,数组等)来检测此问题。
解决方案:使用 StringBuilder 作为结果。
并发:
您希望通过并行执行下载/过滤来加快整个内容的速度。创建一些线程并使用它们运行代码,但在一个大的同步块(基于缓存)内执行所有操作,只是为了“保护缓存免受并发问题的影响”。效果应该是您有效地只使用一个线程,因为所有其他线程都在等待获取缓存上的锁定。
解决方案:仅围绕缓存操作进行同步(例如,使用 'java.util.collections.synchronizedMap())
同步所有微小的代码片段。这应该会扼杀性能,可能会阻止正常的并行执行。如果你足够幸运/聪明,你也可以想出一个死锁。这样做的寓意是:同步不应该是一个临时的事情,在“它不会伤害”的基础上,而是一个经过深思熟虑的事情。
奖金练习:
在开始时填满缓存,之后不要做太多的分配,但仍然在某个地方有一个小的泄漏。通常这种模式不太容易捕捉。您可以使用探查器的“书签”或“水印”功能,该功能应在缓存完成后立即创建。