如何使用两个键(键对,值)创建HashMap?


118

我有一个2D整数数组。我希望将它们放入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;

一种简单的解决方案是将一个键映射到其他哈希图中。
Mihai8年

1
请不要回答问题中的问题。您的编辑很有趣,请随时将其发布为答案。
Ole VV

@Crocode哇!“编辑”中答案背后的数学方法令人眼花ill乱。只是想知道它是否通常适用于任意两个整数i和j。
likejudo

如果i和j是N的倍数,@ Crocode会循环吗?
likejudo

Answers:


190

有几种选择:

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使用下方的地图

N尺寸

请注意,特殊Key类是缩放到n维的唯一方法。您可能还会考虑:

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

但这从性能角度以及可读性和正确性(强制列表大小的简便方法)方面都是可怕的。

也许看一下Scala,那里有元组和case类(用单线替换整个Key类)。


3
嗨,其他人在执行hashCode时具有x'或两个值。为什么使用31?我认为它与32位整数有关,但是当我考虑时这没有意义,因为x = 1和y = 0仍然映射到与x = 0和y = 31相同的哈希码
pete

1
@Effective Java第3章s 9.中的@pete Joshua Bloch建议:“ 1。将一个恒定的非零值(例如17)存储在一个称为result的int变量中”,如果它是素数 参见:stackoverflow.com/questions/3613102/...
fncomp

2
为什么不使用Wrapper键对象Map.Entry<K, V>作为键?
罗兰

1
Map<Pair<Key1, Key2>, Value>
Joaquin Iurchuk '18

1
请注意,hashCode()也可以使用单行来实现Objects.hash(x,y)
xdavidliu

23

创建自己的密钥对对象时,您应该面对一些问题。

首先,您应该意识到实现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()也要知道它的作用,并要知道创建密钥的方式,因为它们是对象。如果语句导致您总是会有相同的结果,那么您通常不需要做很多事情。

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

而不是if (getClass() != obj.getClass())在你equals()更好地利用if (!(obj instanceof this))。即使对于子类也将有效。

因此,您唯一需要比较的实际上是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公共访问级别,因为它们是最终的并且不包含敏感信息。我不是100%肯定是否private访问级别正常工作在任何铸造时情况ObjectKey

如果您对final感到疑惑,那么我将声明为final的任何实例声明为实例化时设置的值,并且永远不会更改-因此是一个对象常量。


7

您不能具有带有多个键的哈希映射,但是可以有一个采用多个参数作为键的对象。

创建一个名为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<Index, Value>得到您的结果。:)


4
您需要覆盖hashCodeequals
汤姆·霍顿

4
hashCode实现无法区分(2,1)和(1,2)
user1947415

1
那是一次碰撞。哈希码不需要为每个不同的对象保证不同的值。@ user1947415
Ajak6'9

6

在通用集合MultiKeyMap中实现


@Wilson我现在修复了链接,等待同行审阅
Computingfreak

@computingfreak似乎很受欢迎。欢呼!注意,这是最好的答案恕我直言。除非您愿意花费数小时与专家Apache工程师竞争一些非常有用(一如既往)但最终会变得平凡的功能。
麦克啮齿动物

4

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

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

或地图:

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

2
仅当您不关心此处的性能或内存使用(即,地图很小)或具有多个具有相同第一个索引的键时,才使用地图地图-因为此解决方案意味着要为此付出HashMap对象的开销每个唯一的第一索引。
BeeOnRope

1
为了改善此答案,这里是有关覆盖hashCodeequals方法的信息。
Pshemo


1

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

class Index2D {
  int first, second;

  // overrides equals and hashCode properly here
}

注意覆盖equals()hashCode()正确。如果这似乎需要大量工作,则可以考虑使用一些现成的通用容器,例如Pair由apache commons提供的容器。

这里也有很多类似的问题,还有其他一些想法,例如使用番石榴的 Table,尽管允许键具有不同的类型,但根据我的理解,您的键都是整数,因此在您的情况下可能会过分(在内存使用和复杂性方面)。


1

如果它们是两个整数,则可以尝试快速又肮脏的技巧:Map<String, ?>使用key作为i+"#"+j

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


2
真是个坏主意。首先,这很糟糕。其次,该技术将被复制到其他类型,在这些类型中,不同的键可能会映射到同一键上,String从而产生滑稽的结果。
Tom Hawtin-大头钉

这在我看来,i#j = j#ii == j这样使用min/max技巧是不行的。
Matthieu 2014年

1
@Matthieu 5#5和swaped之间有什么区别5#5
2015年

@enrey没有。这就是我所指出的。这实际上取决于您对按键的了解。
Matthieu 2015年

@Matthieu啊哈,我明白你的意思了。我认为@arutaku的意思是,当您想5#3使用一样的哈希值时3#5,然后使用min / max来强制执行3#5此顺序。
enrey,2015年

1

您也可以为此使用guava 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");

1

您可以这样创建关键对象:

公共类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应该是不变的。只有这样,您才能构造稳定的键对象。


1

我们可以创建一个类以传递多个键或值,并且该类的对象可以用作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();

        }

    }

    }

 }

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.