将数据作为键存储在具有空/空值的HashMap中是一个好主意吗?

doz*_*zer 63 java arrays performance hashmap asymptotic-complexity

我最初写了一个ArrayList并存储了唯一值(用户名,即Strings).我后来需要使用它ArrayList来搜索用户是否存在.这是O(n)为了搜索.

我的技术负责人希望我将其更改为a HashMap并将用户名存储为数组中的键,并将值存储为空Strings.

所以,在Java中 -

hashmap.put("johndoe","");
Run Code Online (Sandbox Code Playgroud)

我可以通过运行来查看此用户是否存在 -

hashmap.containsKey("johndoe"); 
Run Code Online (Sandbox Code Playgroud)

这是O(1)对的?

我的主管说这是一种更有效的方法来实现这一点,这对我来说很有意义,但是将hash/empty作为值放在hashmap中并将其中的元素作为键存放似乎有点过时了.

我的问题是,这是一个好方法吗?效率节拍ArrayList#contains或一般的阵列搜索.有用.我担心的是,我没有看到其他人在搜索后这样做.我可能在某个地方错过了一个明显的问题,但我看不到它.

Sto*_*ica 101

由于您有一组唯一值,因此a Set是适当的数据结构.您可以将值放在内部HashSet,即Set接口的实现.

我的主管说这是一种更有效的方法来实现这一点,这对我来说很有意义,但是将hash/empty作为值放在hashmap中并将其中的元素作为键存放似乎有点过时了.

领导的建议是有缺陷的.Map对此不是正确的抽象,Set是.A Map适用于键值对.但是你没有价值观,只有钥匙.

用法示例:

Set<String> users = new HashSet<>(Arrays.asList("Alice", "Bob"));

System.out.println(users.contains("Alice"));
// -> prints true

System.out.println(users.contains("Jack"));
// -> prints false
Run Code Online (Sandbox Code Playgroud)

使用a Map会很尴尬,因为值应该是什么类型?这个问题在你的用例中毫无意义,因为你只有键,而不是键值对.使用a Set,你不需要问,使用是完全自然的.

这是O(1)对吗?

是的,搜索a HashMap或a HashSet是O(1)摊销的最坏情况,而在a List或数组中搜索是O(n)最坏情况.


一些评论指出a HashSet是以实施方式实施的HashMap.这很好,在那个抽象层面.在手头任务的抽象层面---存储一组独特的用户名,使用集合是一种自然的选择,比地图更自然.

  • 请注意,一个HashSet作为与一个空对象(对于所有的值的单个实例)作为值一个HashMap实现. (13认同)
  • @Janos:我不会说领导的建议存在缺陷......这个想法是正确的,只是数据结构不是最佳选择.即使使用空值,Map仍然使用哈希作为键的查找方法.所以它比数组迭代更快.技术主管可能来自Perl背景 - 通常的做法是使用带有空(或虚拟)值的散列来执行O(1)密钥存在检查.Perl没有Set数据结构. (5认同)
  • 有一件事我会提,而我这个答案同意,你应该用你的技术导澄清,如果有想要一个'Map`背后的一个原因.也许他们假设您将使用此地图存储与用户ID相关的其他信息?如果有任何其他原因都在内存中拥有的用户相关的数据,你可能要存储在那儿,而不是创造另一个集合其他地方,复制代码. (2认同)
  • @JörgWMittag更准确地说,在Java 8中,如果密钥是可比较的,那么它是O(log n)最坏的情况; 如果特定存储桶过载,其链表冲突处理将切换到TreeSet样式存储桶.这是为了避免攻击者可以定义的DoS攻击,例如,查询字符串条目故意冲突的URL,将预期的O(1)转换为O(n)(然后将预期的O(n)循环转换为O (n ^ 2)等). (2认同)

Era*_*ran 37

这基本上是如何HashSet实现的,所以我想你可以说这是一个很好的方法.您也可以使用HashSet而不是HashMap使用空值.

例如 :

HashSet的实施add

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
Run Code Online (Sandbox Code Playgroud)

map支持在哪里HashMap,PRESENT是虚拟值.

我担心的是,我没有看到其他人在搜索后这样做.我可能在某个地方错过了一个明显的问题,但我看不到它.

正如我所提到的,JDK的开发人员正在使用相同的方法.

  • @dozer HashSet已经是一个现有的类,它是JDK的一部分,那么为什么要重新发明轮子呢?它不会使数组成为冗余,因为当元素数量固定时,数组更有效,并且数组(以及ArrayLists)允许重复. (21认同)
  • @dozer"它不会使数组冗余吗?" 数组中的元素的局部性使得迭代比与分别分配各元素(地图,集合,列表等)的数据结构更快.通常,连续的数组元素被缓存在一起,因为它们在内存中很接近,从而最小化了内存访 (5认同)
  • @dozer Arrays不只是存储元素,而是将它们存储在固定的位置; 在某种意义上说,它们相关联的数字(的位置)到该元件.同样的信息可以存储在一个地图,但(如果该元素是在自己的岗位不疏)的效率要低得多方式 (3认同)