只是为了验证我的说法,即常规 TreeMap 应该足以满足任何包含多达 1,000,000 个条目的存储桶,这里有一个非常简单的测试用例,它给出了一些数字(注意:这并不是作为一个微基准,它只是为了获得一个感觉这个问题的严重性)。
我使用随机生成的 UUID 来模拟键(如果你用斜杠替换破折号,你甚至会得到一种目录结构)。之后,我把它们放到了一个普通的java.util.TreeMap中,最后用map.subMap(fromKey, toKey)查询它们。
public static void main(String[] args) {
TreeMap<String, Object> map = new TreeMap<String, Object>();
int count = 1000000;
ArrayList<String> uuids;
{
System.out.print("generating ... ");
long start = System.currentTimeMillis();
uuids = new ArrayList<String>(count);
for (int i = 0; i < count; i++) {
uuids.add(UUID.randomUUID().toString());
}
System.out.println((System.currentTimeMillis() - start) + "ms");
}
{
System.out.print("inserting .... ");
long start = System.currentTimeMillis();
Object o = new Object();
for (int i = 0; i < count; i++) {
map.put(uuids.get(i), o);
}
System.out.println((System.currentTimeMillis() - start) + "ms");
}
{
System.out.print("querying ..... ");
String from = "be400000-0000-0000-0000-000000000000";
String to = "be4fffff-ffff-ffff-ffff-ffffffffffff";
long start = System.currentTimeMillis();
long matches = 0;
for (int i = 0; i < count; i++) {
Map<String, Object> result = map.subMap(from, to);
matches += result.size();
}
System.out.println((System.currentTimeMillis() - start) + "ms (" + matches/count
+ " matches)");
}
}
这是我机器的一些示例输出(1,000,000 个键,1,000,000 个范围查询):
generating ... 6562ms
inserting .... 2933ms
querying ..... 5344ms (229 matches)
插入 1 个键平均需要 0.003 毫秒(当然,最后肯定会更多),而查询具有 229 个匹配项的子范围每次查询需要 0.005 毫秒。这是一些相当理智的表现,不是吗?
将数量增加到10,000,000个key和query后,数量如下:
generating ... 59562ms
inserting .... 47099ms
querying ..... 444119ms (2430 matches)
插入 1 个键平均需要 0.005 毫秒,而查询具有 2430 个匹配项的子范围每次查询需要 0.044 毫秒。即使查询速度慢了 10 倍(最后,它会遍历所有始终为 O(n) 的匹配项),但性能仍然不算太差。
由于 S3 是一项云服务,我认为无论如何它都会受到网络的限制。因此,并不迫切需要极其花哨的数据结构来获得所需的性能。尽管如此,我的测试用例还是缺少一些特性,最显着的是并发性和持久性。尽管如此,我认为我已经证明,对于这个用例来说,一个常规的树结构就足够了。如果您想做一些花哨的事情,请尝试使用子树读写锁定,也许可以替代 .subMap(fromKey, toKey);