如何创建具有两个键(键对、值)的 HashMap?

Posted

技术标签:

【中文标题】如何创建具有两个键(键对、值)的 HashMap?【英文标题】:How to create a HashMap with two keys (Key-Pair, Value)? 【发布时间】:2013-01-18 15:16:12 【问题描述】:

我有一个二维整数数组。我希望将它们放入 HashMap 中。但我想根据数组索引访问 HashMap 中的元素。比如:

对于 A[2][5],map.get(2,5) 返回与该键关联的值。但是如何使用一对键创建一个 hashMap 呢?或者一般来说,多个键:Map<((key1, key2,..,keyN), Value),我可以使用 get(key1,key2,...keyN) 访问元素。

编辑:发布问题 3 年后,我想再补充一点

我遇到了NxN matrix 的另一种方式。

数组索引,ij 可以通过以下方式表示为单个 key

int key = i * N + j;
//map.put(key, a[i][j]); // queue.add(key); 

并且可以通过这种方式从key 检索索引:

int i = key / N;
int j = key % N;

【问题讨论】:

一个简单的解决方案是将一个键映射到另一个哈希图中。 请不要回答问题中的问题。您的编辑很有趣,因此请随时将其发布为答案。 @Crocode 哇!编辑中答案背后的数学是闪烁的。只是想知道它是否适用于任何两个整数 i 和 j。 @Crocode 如果 i 和 j 是 N 的倍数,它们会循环吗? 【参考方案1】:

有几种选择:

2 维

地图地图

Map<Integer, Map<Integer, V>> map = //...
//...

map.get(2).get(5);

包装键对象

public class Key 

    private final int x;
    private final int y;

    public Key(int x, int y) 
        this.x = x;
        this.y = y;
    

    @Override
    public boolean equals(Object o) 
        if (this == o) return true;
        if (!(o instanceof Key)) return false;
        Key key = (Key) o;
        return x == key.x && y == key.y;
    

    @Override
    public int hashCode() 
        int result = x;
        result = 31 * result + y;
        return result;
    


在这里实现equals()hashCode() 至关重要。然后你只需使用:

Map<Key, V> map = //...

和:

map.get(new Key(2, 5));

Table 来自番石榴

Table<Integer, Integer, V> table = HashBasedTable.create();
//...

table.get(2, 5);

Table 在下面使用 map of maps

N 个维度

请注意,特殊的 Key 类是唯一可以扩展到 n 维的方法。您还可以考虑:

Map<List<Integer>, V> map = //...

但是从性能的角度来看,这很糟糕,可读性和正确性也很糟糕(没有简单的方法来强制列表大小)。

也许看看你有元组和 case 类的 Scala(用单线替换整个 Key 类)。

【讨论】:

嗨,其他人在做hashCode时有x'或这两个值。为什么用 31?我认为它与 32 位整数有关,但是当我想到它时它没有意义,因为 x=1 和 y=0 仍然映射到与 x=0 和 y=31 相同的哈希码跨度> @pete Joshua Bloch,在 Effective Java ch 3. s 9. 中,建议“1. 将一些常量非零值,比如 17,存储在一个名为 result ... 的 int 变量中”,其中如果它是素数,则在碰撞方面效果更好。另见:***.com/questions/3613102/… 为什么不用Map.Entry&lt;K, V&gt;作为键而不是Wrapper键对象? Map&lt;Pair&lt;Key1, Key2&gt;, Value&gt; 呢? 注意hashCode()也可以用单行实现Objects.hash(x,y)【参考方案2】:

当您创建自己的密钥对对象时,您应该面对一些事情。

首先,您应该了解实现hashCode()equals()。您需要这样做。

其次,在实现hashCode() 时,请确保您了解它的工作原理。给定的用户示例

public int hashCode() 
    return this.x ^ this.y;

实际上是你能做的最糟糕的实现之一。原因很简单:你有很多相等的哈希值! hashCode() 应该返回往往很少见的 int 值,最好是唯一的。使用这样的东西:

public int hashCode() 
  return (X << 16) + Y;

这速度很快,并为 -2^16 和 2^16-1(-65536 到 65535)之间的键返回唯一的哈希值。这几乎适用于任何情况。很少你会超出这个界限。

第三,在实现equals() 时,还要知道它的用途并注意如何创建密钥,因为它们是对象。通常你会做不必要的 if 语句,因为你总是会得到相同的结果。

如果您像这样创建密钥:map.put(new Key(x,y),V);,您将永远不会比较您的密钥的引用。因为每次您想要访问地图时,您都会执行map.get(new Key(x,y)); 之类的操作。因此,您的 equals() 不需要像 if (this == obj) 这样的声明。它永远不会发生。

在您的equals() 中最好使用if (!(obj instanceof this)),而不是if (getClass() != obj.getClass())。它甚至对子类也有效。

所以你唯一需要比较的是X和Y。所以在这种情况下最好的equals()实现是:

public boolean equals (final Object O) 
  if (!(O instanceof Key)) return false;
  if (((Key) O).X != X) return false;
  if (((Key) O).Y != Y) return false;
  return true;

所以最后你的关键类是这样的:

public class Key 

  public final int X;
  public final int Y;

  public Key(final int X, final int Y) 
    this.X = X;
    this.Y = Y;
  

  public boolean equals (final Object O) 
    if (!(O instanceof Key)) return false;
    if (((Key) O).X != X) return false;
    if (((Key) O).Y != Y) return false;
    return true;
  

  public int hashCode() 
    return (X << 16) + Y;
  


您可以为维度索引XY 提供公共访问级别,因为它们是最终的并且不包含敏感信息。在将Object 转换为Key 时,我不能100% 确定private 访问级别在any 情况下是否正常工作。

如果您想知道决赛,我将任何东西声明为 final,该值在实例化时设置并且永远不会改变 - 因此是一个对象常量。

【讨论】:

【参考方案3】:

你不能有一个带有多个键的哈希映射,但你可以有一个对象,它以多个参数作为键。

创建一个名为 Index 的对象,该对象采用 x 和 y 值。

public class Index 

    private int x;
    private int y;

    public Index(int x, int y) 
        this.x = x;
        this.y = y;
    

    @Override
    public int hashCode() 
        return this.x ^ this.y;
    

    @Override
    public boolean equals(Object obj) 
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Index other = (Index) obj;
        if (x != other.x)
            return false;
        if (y != other.y)
            return false;
        return true;
    

然后让你的HashMap&lt;Index, Value&gt; 得到你的结果。 :)

【讨论】:

你需要覆盖hashCodeequals hashCode 实现没有区分 (2,1) 和 (1,2) 那是碰撞。 Hashcode 不需要为每个不同的对象保证不同的值。 @user1947415【参考方案4】:

在公共集合中实现MultiKeyMap

【讨论】:

@Wilson 我现在修复了链接,等待同行评审 @computingfreak 似乎得到了一个有利的看法。欢呼!注意这是最好的答案恕我直言。除非您喜欢花几个小时与 Apache 专家工程师就一些非常有用(一如既往)但最终很普通的功能进行竞争。【参考方案5】:

两种可能性。使用组合键:

class MyKey 
    int firstIndex;
    int secondIndex;
    // important: override hashCode() and equals()

或地图地图:

Map<Integer, Map<Integer, Integer>> myMap;

【讨论】:

如果您不关心这里的性能或内存使用(即地图很小),或者有许多具有相同第一个索引的键 - 因为此解决方案意味着为每个唯一的第一个索引支付 HashMap 对象的开销。 要改进这个答案here 是关于覆盖hashCodeequals 方法的信息。【参考方案6】:

使用Pair 作为HashMap 的键。 JDK 没有 Pair,但您可以使用 3rd 方库,例如 http://commons.apache.org/lang 或编写您自己的 Pair taype。

【讨论】:

【参考方案7】:

Java 7+ 包含一个新的Map.Entry&lt;K,V&gt; 类,您可以将其用作地图的键(或集合的条目)。它还包含一个Map.entry(K k, V v) 以轻松创建新的Map.Entry 对象。

用法:

Map<Map.Entry<Integer,Integer>, Integer> map = new HashMap<>();
map.put(Map.entry(1, 2), 0);

javafx.util中还有Pair&lt;K, V&gt;

Map<Pair<Integer,Integer>, Integer> map = new HashMap<>();
map.put(new Pair(1, 2), 0);

【讨论】:

【参考方案8】:

创建一个代表复合键的值类,例如:

class Index2D 
  int first, second;

  // overrides equals and hashCode properly here

注意正确覆盖equals()hashCode()。如果这看起来工作量很大,您可以考虑一些现成的通用容器,例如 apache commons 提供的 Pair 等。

这里也有很多similar questions,还有其他想法,比如使用Guava的Table,虽然允许键有不同的类型,这在你的情况下可能是矫枉过正(在内存使用和复杂性方面),因为我理解你的键都是整数。

【讨论】:

【参考方案9】:

如果它们是两个整数,您可以尝试一个快速而肮脏的技巧:Map&lt;String, ?&gt;,使用密钥作为i+"#"+j

如果密钥i+"#"+jj+"#"+i 相同,请尝试min(i,j)+"#"+max(i,j)

【讨论】:

真是个坏主意。首先,这很糟糕。其次,这种技术将被复制到其他类型,其中不同的键可能被映射到同一个 String 上,结果很有趣。 在我看来 i#j = j#ii == j 所以使用 min/max 技巧是行不通的。 @Matthieu 5#55#5 有什么区别? @enrey 无。这就是我要指出的。这真的取决于你对钥匙的了解。 @Matthieu aha 我明白你的意思了。我认为@arutaku 的意思是当您希望5#3 具有与3#5 相同的哈希值时,然后您使用 min/max 按此顺序强制执行3#5【参考方案10】:

您也可以为此使用 guava Table 实现。

Table 表示一个特殊的映射,其中可以以组合方式指定两个键来引用单个值。它类似于创建地图的地图。

//create a table
  Table<String, String, String> employeeTable = HashBasedTable.create();

  //initialize the table with employee details
  employeeTable.put("IBM", "101","Mahesh");
  employeeTable.put("IBM", "102","Ramesh");
  employeeTable.put("IBM", "103","Suresh");

  employeeTable.put("Microsoft", "111","Sohan");
  employeeTable.put("Microsoft", "112","Mohan");
  employeeTable.put("Microsoft", "113","Rohan");

  employeeTable.put("TCS", "121","Ram");
  employeeTable.put("TCS", "122","Shyam");
  employeeTable.put("TCS", "123","Sunil");

  //get Map corresponding to IBM
  Map<String,String> ibmEmployees =  employeeTable.row("IBM");

【讨论】:

【参考方案11】:

你可以像这样创建你的关键对象:

公共类 MapKey

public  Object key1;
public Object key2;

public Object getKey1() 
    return key1;


public void setKey1(Object key1) 
    this.key1 = key1;


public Object getKey2() 
    return key2;


public void setKey2(Object key2) 
    this.key2 = key2;


public boolean equals(Object keyObject)

    if(keyObject==null)
        return false;

    if (keyObject.getClass()!= MapKey.class)
        return false;

    MapKey key = (MapKey)keyObject;

    if(key.key1!=null && this.key1==null)
        return false;

    if(key.key2 !=null && this.key2==null)
        return false;

    if(this.key1==null && key.key1 !=null)
        return false;

    if(this.key2==null && key.key2 !=null)
        return false;

    if(this.key1==null && key.key1==null && this.key2 !=null && key.key2 !=null)
        return this.key2.equals(key.key2);

    if(this.key2==null && key.key2==null && this.key1 !=null && key.key1 !=null)
        return this.key1.equals(key.key1);

    return (this.key1.equals(key.key1) && this.key2.equals(key2));


public int hashCode()
    int key1HashCode=key1.hashCode();
    int key2HashCode=key2.hashCode();
    return key1HashCode >> 3 + key2HashCode << 5;

这样做的好处是:它将始终确保您也涵盖了 Equals 的所有场景。

注意:您的 key1 和 key2 应该是不可变的。只有这样你才能构造一个稳定的key Object。

【讨论】:

【参考方案12】:

我们可以创建一个类来传递多个键或值,并且该类的对象可以用作map中的参数。

import java.io.BufferedReader; 
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

 public class key1 
    String b;
    String a;
    key1(String a,String b)
    
        this.a=a;
        this.b=b;
    
  

public class read2 

private static final String FILENAME = "E:/studies/JAVA/ReadFile_Project/nn.txt";

public static void main(String[] args) 

    BufferedReader br = null;
    FileReader fr = null;
    Map<key1,String> map=new HashMap<key1,String>();
    try 

        fr = new FileReader(FILENAME);
        br = new BufferedReader(fr);

        String sCurrentLine;

        br = new BufferedReader(new FileReader(FILENAME));

        while ((sCurrentLine = br.readLine()) != null) 
            String[] s1 = sCurrentLine.split(",");
            key1 k1 = new key1(s1[0],s1[2]);
            map.put(k1,s1[2]);
        
        for(Map.Entry<key1,String> m:map.entrySet())  
            key1 key = m.getKey();
            String s3 = m.getValue();
               System.out.println(key.a+","+key.b+" : "+s3);  
                
  //               
         catch (IOException e) 

        e.printStackTrace();

     finally 

        try 

            if (br != null)
                br.close();

            if (fr != null)
                fr.close();

         catch (IOException ex) 

            ex.printStackTrace();

        

    

    

 

【讨论】:

【参考方案13】:

您可以从以下链接下载它: https://github.com/VVS279/DoubleKeyHashMap/blob/master/src/com/virtualMark/doubleKeyHashMap/DoubleKeyHashMap.java

https://github.com/VVS279/DoubleKeyHashMap

可以使用双键:value hashmap,

   DoubleKeyHashMap<Integer, Integer, String> doubleKeyHashMap1 = new 
   DoubleKeyHashMap<Integer, Integer, String>();

   DoubleKeyHashMap<String, String, String> doubleKeyHashMap2 = new 
   DoubleKeyHashMap<String, String, String>();

【讨论】:

【参考方案14】:

使用 org.apache.commons.lang3.tuple.Pair 非常简单;

   Map<String, Pair<String, Integer>> map= new HashMap<>(); 
   map.put("key", Pair.of("a", 1)); 
   int value = map.get("key").getRight();
   

【讨论】:

以上是关于如何创建具有两个键(键对、值)的 HashMap?的主要内容,如果未能解决你的问题,请参考以下文章

通过具有日期值的单个键对对象数组进行排序

如何通过特定对象键对数组中的值求和?

如何使用特定键对数组值求和

PHP按另一个数组中的特定键对数组进行排序[重复]

按值然后键对字典进行排序

BigQuery 和 Firebase - 基于键对事件值求和