将数据存储为具有空/空值的 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 的数据结构时,这是一个有效的问题。 HashSetHashMap.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) 对吗?

是的,在 HashMapHashSet 中搜索是 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 中使用添加到 MapcomputeIfAbsent 等方法【参考方案2】:

这基本上是HashSet 的实现方式,所以我想你可以说这是一个好方法。您不妨使用 HashSet 而不是带有空值的 HashMap

例如:

HashSetadd的实现是

public boolean add(E e) 
    return map.put(e, PRESENT)==null;

其中map 是支持HashMapPRESENT 是一个虚拟值。

我担心的是,我没有看到其他人在搜索后这样做。我可能在某处遗漏了一个明显的问题,但我看不到它。

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

【讨论】:

谢谢,为什么不直接使用 HashMap.put("aaa","") 的现有实现而不是 HashSet?另外既然这种方法很好,那不是让数组变得多余吗? @dozer HashSet 已经是 JDK 中的一个现有类,那么为什么要重新发明***呢?它不会使数组变得多余,因为当元素数量固定时,数组效率更高,并且数组(以及 ArrayLists)允许重复。 @dozer 数组不只是存储元素,它们将它们存储在一个固定的位置;从某种意义上说,它们将数字(位置)与元素相关联。相同的信息可以存储在 Map 中,但是(如果元素的位置不是稀疏的)以一种效率低得多的方式 @dozer “不是让数组变得多余吗?”数组中元素的局部性使得迭代比使用单独分配每个元素的数据结构(映射、集合、列表等)更快。通常连续的数组元素被缓存在一起,因为它们在内存中很接近,从而最大限度地减少内存访问。

以上是关于将数据存储为具有空/空值的 HashMap 中的键是一个好主意吗?的主要内容,如果未能解决你的问题,请参考以下文章

具有空键和空值的 HashMap

复制没有空值的 NSDictionary?

VBScript:将具有空值的参数传递给存储过程?

sql语句返回类型为hashmap的时候空值的字段会显示吗

用python.检查“影片名称”字段为空值的+数据,给该字段填充数+据"unnamed"?

Pyspark 将 rdd 转换为具有空值的数据帧