我需要反复(成千上万次)从包含数十万个对象的Collection
中检索一个元素(每次不同)。
执行此检索操作的最快方法是什么?目前我的Collection
是一个List
并且我一直在迭代它直到找到元素,但有更快的方法吗?可能使用Map
?我当时想要这样做:
Map
中,其中键是Object的id字段,而Object本身就是值。get(id)
上执行Map
应该比循环List
快得多。 HashMap
还是TreeMap
? - 我的对象没有特别的顺序。对此事的任何建议都将不胜感激!
最后注意:如果外部图书馆提供了回答这个问题的工具,我很乐意接受它!
答案 0 :(得分:5)
根据Tree Map
的文件(强调我自己):
地图根据其键的自然顺序排序, 或者在地图创建时提供的比较器,具体取决于哪个 使用构造函数。
在您的情况下,您声明项目没有特定的顺序,并且您似乎没有遵循任何特定的顺序,而是能够尽快检索数据。
HashMaps
提供不断的阅读时间,但不保证顺序,所以我认为你应该选择HashMaps
:
此课程不保证地图的顺序;在 特别是,它不保证订单将保持不变 随着时间的推移。 此实现提供了恒定时间性能 假设哈希函数,基本操作(获取和放置) 将元素正确地分散在桶中。
作为旁注,此内存占用量可以非常快,因此查看数据库方法也许是一个好主意,也许可以使用类似缓存的机制来处理更频繁使用的信息。
答案 1 :(得分:3)
我已经创建了代码,用于测试BinarySearch,TreeMap和HashMap对于给定问题的性能。
如果您每次都在重建集合,HashMap是最快的(即使使用标准的Object&hashCode()实现!),sort +数组二进制搜索排在第二位,TreeMap排在最后(由于复杂的重建)过程)。
proc array: 2395
proc tree : 4413
proc hash : 1325
如果你没有重建集合,HashMap仍然是最快的,数组的二进制搜索是第二个,而TreeMap是最慢的,但速度只比数组略低。
proc array: 506
proc tree : 561
proc hash : 122
测试代码:
public class SearchSpeedTest {
private List<DataObject> data;
private List<Long> ids;
private Map<Long, DataObject> hashMap;
private Map<Long, DataObject> treeMap;
private int numRep;
private int dataAmount;
private boolean rebuildEachTime;
static class DataObject implements Comparable<DataObject>{
Long id;
public DataObject(Long id) {
super();
this.id = id;
}
public DataObject() {
// TODO Auto-generated constructor stub
}
@Override
public final int compareTo(DataObject o) {
return Long.compare(id, o.id);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public void dummyCode() {
}
}
@FunctionalInterface
public interface Procedure {
void execute();
}
public void testSpeeds() {
rebuildEachTime = true;
numRep = 100;
dataAmount = 60_000;
data = new ArrayList<>(dataAmount);
ids = new ArrayList<>(dataAmount);
Random gen = new Random();
for (int i=0; i< dataAmount; i++) {
long id = i*7+gen.nextInt(7);
ids.add(id);
data.add(new DataObject(id));
}
Collections.sort(data);
treeMap = new TreeMap<Long, DataObject>();
populateMap(treeMap);
hashMap = new HashMap<Long, SearchSpeedTest.DataObject>();
populateMap(hashMap);
Procedure[] procedures = new Procedure[] {this::testArray, this::testTreeMap, this::testHashMap};
String[] names = new String[] {"array", "tree ", "hash "};
for (int n=0; n<procedures.length; n++) {
Procedure proc = procedures[n];
long startTime = System.nanoTime();
for (int i=0; i<numRep; i++) {
if (rebuildEachTime) {
Collections.shuffle(data);
}
proc.execute();
}
long endTime = System.nanoTime();
long diff = endTime - startTime;
System.out.println("proc "+names[n]+":\t"+(diff/1_000_000));
}
}
void testHashMap() {
if (rebuildEachTime) {
hashMap = new HashMap<Long, SearchSpeedTest.DataObject>();
populateMap(hashMap);
}
testMap(hashMap);
}
void testTreeMap() {
if (rebuildEachTime) {
treeMap = new TreeMap<Long, SearchSpeedTest.DataObject>();
populateMap(treeMap);
}
testMap(treeMap);
}
void testMap(Map<Long, DataObject> map) {
for (Long id: ids) {
DataObject ret = map.get(id);
ret.dummyCode();
}
}
void populateMap(Map<Long, DataObject> map) {
for (DataObject dataObj : data) {
map.put(dataObj.getId(), dataObj);
}
}
void testArray() {
if (rebuildEachTime) {
Collections.sort(data);
}
DataObject key = new DataObject();
for (Long id: ids) {
key.setId(id);
DataObject ret = data.get(Collections.binarySearch(data, key));
ret.dummyCode();
}
}
public static void main(String[] args) {
new SearchSpeedTest().testSpeeds();
}
}
答案 2 :(得分:2)
HashMap一般来说效率更高,所以每当你不关心键的顺序时就使用它。
当你想要按照键排序的Map中的条目而不是使用TreeMap时,但是在你的情况下排序将是开销,因为你不需要任何微粒订单。
答案 3 :(得分:0)
如果您有一个很好的方法来定义地图的关键字,您可以使用地图。在最坏的情况下,您可以将对象用作键和值。
由于订购并不重要,请使用HashMap
。要在TreeMap
中维护订单,添加元素会产生额外费用,因为必须将其添加到正确的位置。