首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将数据存储在可由N个键检索的映射中

将数据存储在可由N个键检索的映射中
EN

Code Review用户
提问于 2019-09-30 14:03:11
回答 1查看 105关注 0票数 2

我经常将数据存储在Map中,需要通过键以外的东西检索这些数据,因此希望编写一个泛型类,允许您定义最多N个键(每个键都有不同的类的可能性)。方法用于检索第一个匹配结果和所有匹配结果。

我通过在内部创建一个映射到数据的ID和映射到其匹配ID列表的键来实现这一点。以下是我的实现:

代码语言:javascript
复制
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类为例:

代码语言:javascript
复制
public class Person {
    private String  firstName;
    private String  lastName;
    private String  streetName;
    private int     doorNumber;
}

MultiKeyMap可以像这样初始化,

代码语言:javascript
复制
MultiKeyMap<Person> map = new MultiKeyMap<>(data, Person::getLastName, Person::getDoorNumber);

这样就可以根据他们的姓氏或门牌号来检索数据。

与传统的嵌套Map方法相比,我对其进行了测试,这似乎在几乎相同的时间或更短的时间内运行(这里只比较检索时间,因为初始化时间不是问题)。

我的问题是:这个代码可以改进吗?是否有些情况下,它可能不起作用?

编辑添加了一个单元测试(基于与以前相同的Person类),以显示如何使用它,以及下面预期的结果。

代码语言:javascript
复制
@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"));
}
EN

回答 1

Code Review用户

发布于 2019-09-30 16:28:25

  1. 我会掉下来
代码语言:javascript
复制
private Map<Long, T>                            dataMap = new HashMap<>();
private long                                    id      = 0;

直接使用

代码语言:javascript
复制
private Map<Class<?>, Map<Object, List<T>>>  keyMaps = new HashMap<>();

您已经在存储指向对象的指针,您不会使用dataMap保存任何内存。

  1. 我希望add()方法也会向keyMaps添加新项。
  2. 为什么不允许同一类型的多个键?也许可以将API更改为如下所示:
代码语言:javascript
复制
MultiKeyMap<Person> map = new MultiKeyMap<>(data);
Map<String, List<Person>> byLast = map.by(Person::getLastName);
Map<String, List<Person>> byFirst = map.by(Person::getFirstName);

另一方面,您只需使用内置在流中的:

代码语言:javascript
复制
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));
票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/229911

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档