我正在编写一个需要比较可变大小的ArrayList中的每个文件的程序。现在,我这样做的方式是通过嵌套的代码循环:
if(tempList.size()>1){
for(int i=0;i<=tempList.size()-1;i++)
//Nested loops. I should feel dirty?
for(int j=i+1;j<=tempList.size()-1;j++){
//*Gets sorted.
System.out.println(checkBytes(tempList.get(i), tempList.get(j)));
}
}
我已经阅读了一些关于嵌套循环必要性的不同意见,我想知道是否有人有更高效的替代方案。
乍看之下,无论哪种方式都需要进行每次比较,所以性能应该相当稳定,但我有点确信有更清洁的方法可以做到这一点。有什么指针吗?
编辑::为清晰起见,这只是功能的一部分。这些文件已根据长度进行比较并放入存储桶中 - 在浏览完集合的映射后,找到一个长度大于1的存储桶,它会运行它。所以 - 这些都是相同大小的文件。在我到达字节之前,我将进行校验和比较,但是现在我只是想清理循环。
此外,圣母这个网站响应速度很快。谢谢,伙计们。
EDIT2 ::对不起,需要进一步澄清:文件处理部分我已经得到了很好的掌握,我认为 - 首先,我按长度进行比较和排序,然后按校验和,然后按字节进行排序 - 我遇到的问题是如何正确处理需要有效地比较ArrayList中的所有文件,假设它们都需要进行比较。如果嵌套循环足够了,那很酷,我只想检查这是一个合适的方法,按惯例。
答案 0 :(得分:3)
一个好的优化是首先计算文件的所有哈希值,然后在列表上做一个循环。
这基本上是因为你无论如何都要检查你的列表中的每一对文件,但是这意味着每对的O(1)复杂度,而不是为你要检查的每一对计算很多东西。
你可以这样:
HashSet<YourFile> fileSet = new HashSet<YourFile>();
ArrayList<YourFile> files = new ArrayList<YourFile>();
class YourFile
{
int hashcode = -1;
public int hashCode()
{
// override it to provide an hashcode based on file contents
// you can also cache it to avoid recalculating anything
if (hashcode == -1)
hashcode = calculateIt();
return hashcode;
}
}
// fill up files
files.add(...);
// do comparisons
for (YourFile f : files)
{
if (fileSet.contains(f))
// f and fileSet.get(f) are equal: this is a tricky utilization of the hashCode() method so be careful about it!
else
{
fileSet.put(f);
// since there's not a file with same hashcode you just add this one
}
}
这实际上会丢弃内部循环,因为当您使用hashSet.contains
时,它将检查所有已添加的文件,但复杂度为O(1)。
正如doublep所述,你必须要小心表演,因为当你清楚地检查字节时,你会在找到两个不同的字节时立即停止,而计算哈希则需要检查整个文件。当你有很多文件或文件相当小时,这将很好。最好的办法是对这两种方法进行基准测试,看看是否存在显着的差异。
答案 1 :(得分:3)
我对您的EDIT2问题的回答分为两部分
部分是如果你有少量文件,那么你的嵌套循环方法应该没问题。效果为O(N**2)
,最佳解决方案为O(N)
。但是,如果N
足够小,那么您使用的方法就不会有太大差异。如果您确定N可能很大,则只需考虑替代解决方案。
第二部分阐述了一种利用文件哈希来获取O(N)
解决方案来检测重复项的算法。这就是之前的答案所提到的。
创建一个FileHash
类来表示文件哈希值。这需要定义实现文件哈希的字节相等的equals(Object)
和hashCode()
方法。
创建HashMap<FileHash, List<File>>
地图实例。
对于输入File
中的每个ArrayList
:
FileHash
对象。FileHash
:(请注意,上面的地图实际上是一个多地图,并且有第三方实现可用;例如在Apache commons集合和Google集合中。为了简单起见,我在上面的表单中提出了算法。 )
一些性能问题:
如果你使用一个好的加密哈希函数来生成你的文件哈希值,那么在3.3中找到列表中包含多个元素的条目的几率非常小,并且可能是字节方式比较文件不会说文件相等也很小。但是,计算加密哈希的成本将大于计算较低质量哈希的成本。
如果使用较低质量的哈希,则可以通过在执行逐字节比较之前查看文件大小来降低比较更多文件的潜在成本。如果您这样做,则可以制作地图类型HashMap<FileHash, List<FileTuple>>
,其中FileTuple
是同时包含File
及其长度的类。
通过使用(例如)每个文件的第一个块的哈希,可以降低哈希的成本。但这增加了两个文件可能具有相同散列但仍然不同的概率;例如在第二个街区。这是否重要取决于文件的性质。 (但是,例如,如果您只检查了源代码文件集合的前256个字节,则可能会发生大量冲突......由于存在相同的版权标题!)
答案 2 :(得分:2)
根据您的具体操作,您可能会因为从不比较不同大小的文件而获得相当大的加速。在相同大小的文件中,只比较具有相同散列(通过任何算法)的文件,如其他答案所示。
编辑:
计算哈希值可能会产生共生效果。首先,如果你只是将文件相互比较,就永远不要这样做:你需要完全读取文件来构建一个哈希值,并且一次读取已经足够用于比较,所以你什么也得不到。
其次,如果您很少期望匹配并且实际上文件会有很大差异(早期),那么无论要比较的文件数量如何,计算哈希都可能适得其反。那是因为在这种情况下失败的比较会提前失败(即不读取整个文件),而对于散列构建,则需要完整读取。或者,您可以构建“部分”哈希(例如,文件的前10kb的哈希值),但是请记住使用所有文件的相等块。
答案 3 :(得分:1)
将所有内容与其他所有内容进行比较必然是O(n²)。但是你可以尝试一些技巧。主要是使比较更便宜;这可以通过为每个文件生成哈希码并首先进行比较来完成,这至少可以避免大多数比较(使用足够好的算法,你几乎可以避免每一个)。如果您不需要保留有关哪些文件相同的信息,您也可以加快速度;生成每个文件的Set
个哈希码,并在最后测试时查看该组的大小是否与文件列表的大小相同。
答案 4 :(得分:1)
一个小小的清理就是删除初始尺寸测试 - 如果尺寸小于2,它将在没有进行任何比较的情况下掉线。在循环中,更好地遵守Java编码约定将比较i < tempList.size()
而不是i <= tempList.size() - 1
- 这将使您的代码更容易让其他程序员理解。这些变化都不会对绩效产生任何影响。
for (int i = 0; i < tempList.size(); i++)
for (int j = i + 1; j < tempList.size(); j++) {
//*Gets sorted.
System.out.println(checkBytes(tempList.get(i), tempList.get(j)));
}