将数据存储为具有空/空值的 HashMap 中的键是一个好主意吗?
Posted
技术标签:
【中文标题】将数据存储为具有空/空值的 HashMap 中的键是一个好主意吗?【英文标题】:Is it a good idea to store data as keys in HashMap with empty/null values? 【发布时间】:2016-12-06 02:20:23 【问题描述】:我最初编写了一个ArrayList
并在其中存储了唯一值(用户名,即Strings
)。后来我需要使用ArrayList
来搜索其中是否存在用户。那是 O(n)
用于搜索。
我的技术主管希望我将其更改为 HashMap
,并将用户名存储为数组中的键,并将值存储为空 Strings
。
所以,在 Java 中 -
hashmap.put("johndoe","");
我可以稍后通过运行查看此用户是否存在 -
hashmap.containsKey("johndoe");
这是O(1)
对吗?
我的负责人说这是一种更有效的方法,这对我来说很有意义,但将 null/empty 作为值放在 hashmap 中并将元素作为键存储在其中似乎有点不对劲。
我的问题是,这是一个好方法吗?效率胜过ArrayList#contains
或一般的数组搜索。有用。
我担心的是,我没有看到其他人在搜索后这样做。我可能在某个地方遗漏了一个明显的问题,但我看不到它。
【问题讨论】:
plus1 因为当一个人不知道 Java 的数据结构时,这是一个有效的问题。HashSet
是HashMap.keySet()
的实现。如果你想把一个地图变成一个集合,你可以使用set = Collections.newSetFromMap(map)
这个问题没有错。这个论坛是错误的地方。 “Stack Overflow 是一个面向专业和爱好者程序员的问答网站”。
如果用户名不区分大小写,map<string,string>
使用名称作为键和值可能有助于映射到用户名的规范表示。
@rdllopes 我向您保证,以及本网站上的几乎所有人,都存在知识空白,有人会声称您“应该知道”。该网站上大量评分最高的问题都属于该类别。对于哪些问题不够明显,不属于这里,您不会成为仲裁者。
【参考方案1】:
由于您有一组唯一值,因此 Set
是合适的数据结构。您可以将您的值放入HashSet
中,这是Set
接口的一个实现。
我的领导说这是一种更有效的方法,这对我来说很有意义,但是将 null/empty 作为值放在 hashmap 中并将元素作为键存储在其中似乎有点不对劲。
领导的建议有缺陷。 Map
不是正确的抽象,Set
是。 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
使用Map
会很尴尬,因为值的类型应该是什么?这个问题在你的用例中没有意义,
因为您只有键,而不是键值对。
有了Set
,你就不用问了,用法很自然。
这是 O(1) 对吗?
是的,在 HashMap
或 HashSet
中搜索是 O(1) 分摊的最坏情况,而在 List
或数组中搜索是 O(n) 最坏情况。
一些cmets指出HashSet
是按照HashMap
来实现的。
没关系,在那个抽象级别。
在手头任务的抽象层次上——
存储唯一用户名的集合,
使用集合是一种自然的选择,比地图更自然。
【讨论】:
我要提一件事,虽然我同意这个答案,但如果有需要Map
的原因,您应该与您的技术主管澄清。也许他们假设您将使用此地图来存储与用户 ID 相关的其他信息?如果有任何其他原因将用户相关数据存储在内存中,您可能希望将其存储在那里,而不是在其他地方创建另一个集合,复制代码。
请注意,HashSet 是作为 HashMap 实现的,其值为空对象(所有值的单个实例)。
@Janos:我不会说领导的建议有缺陷......想法是正确的,只是数据结构不是最佳选择。 Map,即使是空值,仍然使用哈希作为键的查找方法。所以它比数组迭代更快。技术负责人可能来自 Perl 背景——通常的做法是使用具有空(或虚拟)值的散列来进行 O(1) 键存在检查。 Perl 没有 Set 数据结构。
@JörgWMittag 更准确地说,在 Java 8 中,如果键是 Comparable,那么最坏的情况是 O(log n);如果一个特定的桶太重载,它的链表冲突处理将切换到 TreeSet 样式的桶。这是为了避免 DoS 攻击,例如,攻击者可以定义一个 URL,其查询字符串条目故意冲突,将预期的 O(1) 变为 O(n)(然后将预期的 O(n) 循环变为 O (n^2) 等)。
Map
可以比 Set
更好,以防您需要在 Java 8 中使用添加到 Map
的 computeIfAbsent
等方法【参考方案2】:
这基本上是HashSet
的实现方式,所以我想你可以说这是一个好方法。您不妨使用 HashSet
而不是带有空值的 HashMap
。
例如:
HashSet
对add
的实现是
public boolean add(E e)
return map.put(e, PRESENT)==null;
其中map
是支持HashMap
而PRESENT
是一个虚拟值。
我担心的是,我没有看到其他人在搜索后这样做。我可能在某处遗漏了一个明显的问题,但我看不到它。
正如我所提到的,JDK 的开发人员正在使用同样的方法。
【讨论】:
谢谢,为什么不直接使用 HashMap.put("aaa","") 的现有实现而不是 HashSet?另外既然这种方法很好,那不是让数组变得多余吗? @dozer HashSet 已经是 JDK 中的一个现有类,那么为什么要重新发明***呢?它不会使数组变得多余,因为当元素数量固定时,数组效率更高,并且数组(以及 ArrayLists)允许重复。 @dozer 数组不只是存储元素,它们将它们存储在一个固定的位置;从某种意义上说,它们将数字(位置)与元素相关联。相同的信息可以存储在 Map 中,但是(如果元素的位置不是稀疏的)以一种效率低得多的方式 @dozer “不是让数组变得多余吗?”数组中元素的局部性使得迭代比使用单独分配每个元素的数据结构(映射、集合、列表等)更快。通常连续的数组元素被缓存在一起,因为它们在内存中很接近,从而最大限度地减少内存访问。以上是关于将数据存储为具有空/空值的 HashMap 中的键是一个好主意吗?的主要内容,如果未能解决你的问题,请参考以下文章