我经常将数据存储在Map中,需要通过键以外的东西检索这些数据,因此希望编写一个泛型类,允许您定义最多N个键(每个键都有不同的类的可能性)。方法用于检索第一个匹配结果和所有匹配结果。
我通过在内部创建一个映射到数据的ID和映射到其匹配ID列表的键来实现这一点。以下是我的实现:
public class MultiKeyMap<T> {
private Map<Long, T> dataMap = new HashMap<>();
private Map<Class<?>, Map<Object, List<Long>>> keyMaps = new HashMap<>();
private long id = 0;
/**
* Construct a data map with the given data and key function(s)
*
* @param data
* @param keyFunctions
*/
@SafeVarargs
public MultiKeyMap(Collection<T> data, Function<T, ?>... keyFunctions) {
addAll(data);
for (Function<T, ?> f : keyFunctions) {
addKey(f);
}
}
/**
* Add an additional key function to this data map.
*
* @param keyFunction
* @return
*/
public <K> MultiKeyMap<T> addKey(Function<T, K> keyFunction) {
if (keyFunction == null) {
throw new RuntimeException("Key function must not be null");
}
Map<Object, List<Long>> keyMap = new HashMap<>();
Class<?> keyClass = keyFunction.apply(dataMap.values().iterator().next()).getClass();
if (keyMaps.containsKey(keyClass)) {
keyMap = keyMaps.get(keyClass);
}
for (Entry<Long, T> e : dataMap.entrySet()) {
K key = keyFunction.apply(e.getValue());
if (!keyMap.containsKey(key)) {
List<Long> l = new ArrayList<>();
l.add(e.getKey());
keyMap.put(key, l);
} else {
keyMap.get(key).add(e.getKey());
}
}
keyMaps.put(keyClass, keyMap);
return this;
}
/**
* Add a single element to this data map
*
* @param data
*/
public void add(T data) {
if (data == null) {
throw new RuntimeException("Data must not be null");
}
dataMap.put(id++, data);
}
/**
* Add a collection to this data map
*
* @param data
*/
public void addAll(Collection<T> data) {
if (data == null || data.isEmpty()) {
throw new RuntimeException("Data must not be empty or null");
}
for (T t : data) {
dataMap.put(id++, t);
}
}
/**
* Returns true if there is a mapping for the given key
*
* @param key
* @return
*/
public boolean containsKey(Object key) {
return keyMaps.get(key.getClass()).containsKey(key);
}
/**
* Get a single result from the given key
*
* @param key
* @return
*/
public T get(Object key) {
return dataMap.get(keyMaps.get(key.getClass()).get(key).get(0));
}
/**
* Get a list of results from the given key
*
* @param key
* @return
*/
public List<T> getAll(Object key) {
return keyMaps.get(key.getClass()).get(key).stream().map(a -> dataMap.get(a)).collect(Collectors.toList());
}
/**
* Return the size of the map
*
* @return
*/
public int size() {
return dataMap.size();
}
/**
* Returns true if the map is empty
*
* @return
*/
public boolean isEmpty() {
return dataMap.isEmpty();
}
/**
* Return true if this map contains the given value
*
* @param value
* @return
*/
public boolean containsValue(Object value) {
return dataMap.containsValue(value);
}
/**
* Clear this map of all of its data
*/
public void clear() {
dataMap = new HashMap<>();
keyMaps = new HashMap<>();
id = 0;
}
/**
* Return a list of all key sets generated for this map
*
* @return
*/
public List<Set<Object>> keySets() {
return keyMaps.values().stream().map(a -> a.keySet()).collect(Collectors.toList());
}
/**
* Return the key set for the given class
*
* @param keySetClass
* @return
*/
public Set<Object> keySet(Class<?> keySetClass) {
return keyMaps.get(keySetClass).keySet();
}
/**
* Return all values in this map
*
* @return
*/
public Collection<T> values() {
return dataMap.values();
}
}在内部,键类用于填充键映射,以便更快地检索数据,因为定义的键几乎总是来自不同的类。如果不是,这可能会产生额外的搜索结果,但这是可以接受的(例如,如果一个人的密钥被定义为firstName (String.class),第二个键被定义为lastName (String.class)),当搜索" Smith“时,将返回史密斯的所有匹配的名字和姓氏。“约翰·史密斯”和“史密斯·琼斯”)
以Person类为例:
public class Person {
private String firstName;
private String lastName;
private String streetName;
private int doorNumber;
}MultiKeyMap可以像这样初始化,
MultiKeyMap<Person> map = new MultiKeyMap<>(data, Person::getLastName, Person::getDoorNumber);这样就可以根据他们的姓氏或门牌号来检索数据。
与传统的嵌套Map方法相比,我对其进行了测试,这似乎在几乎相同的时间或更短的时间内运行(这里只比较检索时间,因为初始化时间不是问题)。
我的问题是:这个代码可以改进吗?是否有些情况下,它可能不起作用?
编辑添加了一个单元测试(基于与以前相同的Person类),以显示如何使用它,以及下面预期的结果。
@Test
public void tester() {
List<Person> data = new ArrayList<>();
Person p1 = new Person("John", "Smith", "Street", 6);
Person p2 = new Person("Smith", "Jones", "Road", 7);
Person p3 = new Person("Alex", "Brown", "Street", 6);
Person p4 = new Person("Jane", "Smith", "Road", 8);
data.add(p1);
data.add(p2);
data.add(p3);
data.add(p4);
MultiKeyMap<Person> map = new MultiKeyMap<>(data, Person::getDoorNumber, Person::getLastName);
// get where doorNumber=7
Assert.assertEquals(p2, map.get(7));
List<Person> expected = new ArrayList<>();
expected.add(p1);
expected.add(p4);
// get where lastName="Smith"
Assert.assertEquals(expected, map.getAll("Smith"));
}发布于 2019-09-30 16:28:25
private Map<Long, T> dataMap = new HashMap<>();
private long id = 0;直接使用
private Map<Class<?>, Map<Object, List<T>>> keyMaps = new HashMap<>();您已经在存储指向对象的指针,您不会使用dataMap保存任何内存。
MultiKeyMap<Person> map = new MultiKeyMap<>(data);
Map<String, List<Person>> byLast = map.by(Person::getLastName);
Map<String, List<Person>> byFirst = map.by(Person::getFirstName);另一方面,您只需使用内置在流中的:
List<Person> data;
...
Map<String, List<Person>> byLast = data.stream().collect(Collectors.groupingBy(Person::getLastName));
Map<String, List<Person>> byFirst = data.stream().collect(Collectors.groupingBy(Person::getFirstName));https://codereview.stackexchange.com/questions/229911
复制相似问题