迭代HashMap的键范围

3

如何从 HashMap 中迭代特定范围的键?

我的 HashMap 包含键值对,其中键表示 Excel 中的某个行列 (例如 "BM""AT"),而值则是该单元格中的值。

例如,我的表格导入如下:

startH = {
   BQ=2019-11-04, 
   BU=2019-12-02, 
   BZ=2020-01-06, 
   CD=2020-02-03, 
   CH=2020-03-02, 
   CM=2020-04-06
}  

endH = {
   BT=2019-11-25, 
   BY=2019-12-30, 
   CC=2020-01-27, 
   CG=2020-02-24, 
   CL=2020-03-30, 
   CP=2020-04-27
}

我需要使用键范围迭代这两个哈希表,以便按照正确的顺序提取数据。例如,从"BQ""BT"


1
HashMap没有索引。根据底层实现,这也是不可能的。Java HashMaps并不一定由哈希表表示。它可以切换到红黑树,它们根本不提供直接访问。所以,不可能。 - Zabuzard
4
这是一个XY问题。有更好的方法来解决您所描述的问题。例如,通过使用“LinkedHashMap”(记住插入顺序),然后简单地对其进行迭代。 - Zabuzard
您也可以在所需范围内生成密钥,例如从 BABM,然后使用 map.get 简单地访问数据。 - Zabuzard
1
或者可能是 NavigableMap - chrylis -cautiouslyoptimistic-
1
@chrylis-onstrike- 非常好的建议,谢谢。我总是忘记这个不常用的接口 :) 已经添加到我的答案中了。 - Zabuzard
3个回答

3

解释

能否使用索引迭代哈希映射表?

不行。

HashMap 没有索引。根据底层实现的不同,这也是不可能的。Java 的 HashMap 并不一定由哈希表表示。它可以切换到红黑树,而且它们根本不提供直接访问。所以不行,是不可能的。

在这种方法中还存在另一个基本缺陷。 HashMap 不维护任何顺序。对其进行迭代会产生随机顺序,每次启动程序都可能会改变。但是,在这种方法中,您需要插入顺序。幸运的是,LinkedHashMap 可以实现此功能。但它仍然不能提供基于索引的访问。


解决方案

生成

但是,实际上您甚至不想要基于索引的访问。您想检索某个键范围,例如从 "BA""BM"。与 HashMap 一起使用的一个好方法是生成您的键范围,并简单地使用 Map#get 检索数据:

char row = 'B';
char columnStart = 'A';
char columnEnd = 'M';

for (char column = columnStart; columnStart <= columnEnd; column++) {
    String key = Chararcter.toString(row) + column;
    String data = map.get(key);
    ...
}

如果需要适当处理边缘情况,例如环绕字母表(使用'A' + (column%alphabetSize)),可能需要一些charint的强制转换和加法的反向操作,没有测试。

NavigableMap

实际上有一种地图变体可提供您想要的大部分功能。但与简单的HashMap相比,性能成本更高。这个接口称为NavigableMap,而TreeMap类是一个良好的实现。问题在于它需要明确的顺序。不过好消息是,您实际上希望使用String的自然顺序,即词典顺序。

因此,您可以简单地将其与现有数据一起使用,然后使用NavigableMap#subMap方法:

NavigableMap<String, String> map = new TreeMap<>(...);
String startKey = "BA";
String endKey = "BM";

Map<String, String> subMap = map.subMap(startKey, endKey);
for (Entry<String, String> entry : subMap.entrySet()) {
    ...
}

如果你需要多次执行这种类型的请求,这绝对是值得的,并且它是这种用例的完美数据结构。
链式迭代
如前所述,也可以(虽然不太高效)使用LinkedHashMap(以保持插入顺序),然后简单地迭代键范围。但是它有一些主要缺点,例如首先需要通过完全迭代到那里来定位范围的起始位置。并且它依赖于你正确插入它们的事实。
LinkedHashMap<String, String> map = ...
String startKey = "BA";
String endKey = "BM";

boolean isInRange = false;
for (Entry<String, String> entry : map.entrySet()) {
    String key = entry.getKey();
    if (!isInRange) {
        if (key.equals(startKey)) {
            isInRange = true;
        } else {
            continue;
        }
    }

    ...

    if (key.equals(endKey)) {
        break;
    }
}

谢谢您提供的解决方案。我正在使用LinkedHashMap解决我的问题,现在已经可行了。感谢您编辑问题 :) 救了我的一天。 - DevSay
很高兴它能够工作。我可以问一下为什么你使用了三种提议中最差的方法吗? :) - Zabuzard
我一开始使用了LinkedHashMap并得到了预期的输出,后来你添加了NavigableMap。正如建议的那样,我将尝试NavigableMap作为更好的方法。谢谢。 - DevSay

0
// rangeLower and rangeUpper can be arguments
int i = 0;
for (Object mapKey : map.keySet()) {
    if (i < rangeLower || i > rangeUpper) {
        i++; 
        continue;
    }
    // Do something with mapKey
}

以上代码通过获取键集并显式地维护索引来进行迭代,并在每个循环中递增它。另一个选项是使用LinkedHashMap,它维护一个双向链表以保持插入顺序。


实际上,OP 不想按索引迭代,而是按 Excel 行/列范围迭代(例如从 BABMB 行)。 - Zabuzard
1
我重新阅读了它,它有意义。如果楼主想要,我可以删除这个答案。 - Sid
除此之外,我甚至会认为在非 LinkedHashMap 上执行此操作几乎没有意义。由于顺序是随机的,因此永远不应该依赖它。因此,在分享这段代码片段时,建议加上充分的免责声明。 - Zabuzard

0
我不相信你能做到。你提出的算法假设HashMap的键是有序的,但实际上它们并不是有序的。键的顺序不能保证,只有关联本身是有保障的。
你可能可以将数据结构改为类似这样的形式:
ranges = {
   BQ=BT, 
   BU=BY, 
   ....
}  

然后对HashMap键(起始单元格)的迭代将轻松找到匹配的结束单元格。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接