【问题标题】:Why does my HashMap recognize hash keys that aren't supposed to be keys yet?为什么我的 HashMap 识别出不应该是键的散列键?
【发布时间】:2011-03-23 14:30:10
【问题描述】:

我正在编写奥赛罗游戏,并以下列方式存储在HashMap 中找到的可能动作:

Hashmap<Matrix, PossibleMovesVector>

Matrix 是一个包含int[8][8] 的对象,它描述了当前的棋盘情况。它以下列方式覆盖hashCode()equals()。 (这是必要的,因为多维数组的标准 hashCode() 不查看嵌套数组的内容。)

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + Arrays.deepHashCode(board);
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Matrix other = (Matrix) obj;
    if (!Arrays.equals(board, other.board))
        return false;
    return true;
}

这似乎工作正常。但是当我测试它并在我的屏幕上绘制大约 100 块左右的新板时,我的控制台中突然收到以下消息:

已识别的哈希码 xxxxxxxxxx
从 HashMap 获取动作...

但这发生在我知道是新的板子上。难道奥赛罗有这么多的状态,哈希码会重复自己吗?这是我能找到的唯一解释。那,或者我的代码有问题。但我不认为最后一种情况是这样,因为出错需要很长时间。

编辑: 这大致就是我的程序的工作方式。 getMoves() 有点罗嗦,但它所做的只是:检查棋盘是否已经检查过移动,如果没有检查可能的移动。
- 每当玩家移动时,m.board 就会改变。 (而不是制作一个全新的 Matrix 对象。)
- boardHash 包含另一个哈希表(不过我更喜欢一个简单的数组),它存储可能的移动是针对玩家 1 还是玩家 2。在我之前的帖子中,我认为这无关紧要,因此将其简化为单个向量。

代码大致如下:

public void Init(){  
    //Initialize board  
    m_board = new Matrix(new int[8][8]);  
    m_board.Init();  
    //Compute initial possible moves  
    boardHash.put(m_board, new HashMap<Integer, Vector<Matrix>>());  
    boardHash.get(m_board).put(whosTurn, getPossibleMoves(whosTurn));  
}   

    public Vector<Matrix> getPossibleMoves(int forWho) {
        // Assign variable so the code doesn't get too cluttered
        HashMap<Integer, Vector<Matrix>> moveMap = boardHash.get(m_board);

        // Maybe moveMap doesn't even exist is our lookup table yet
        if(moveMap == null){
            System.out.println("\nNever before seen board...");
            moveMap = new HashMap<Integer, Vector<Matrix>>();
            boardHash.put(m_board, moveMap);
            moveMap.put(forWho,getPossibleMoves(forWho));
        }else{
            System.out.println("\nRecognized hashCode "+m_board.hashCode());                
            // If it does exist, maybe no possible moves are stored in it 
            if(moveMap.get(forWho)==null){  
                boardHash.put(m_board, m_board.board);
                //So then make it:
                // System.out.println("No moves for "+side+" yet...");
                Vector<Matrix> v_possMoves = new Vector<Matrix>();
                //For every empty square on the current field...
                for(int column = 0; column<8; column++){
                    for(int row = 0; row<m_board.board[column].length; row++){
                        if(m_board.board[column][row] == 0){
                            //Check if this move is valid, and if it is, return the resulting board
                            Matrix possibleMoveMatrix = new Matrix(makeLines(column,row,forWho));
                            if(possibleMoveMatrix.board != null){
                                //add it to the vector
                                v_possMoves.add(possibleMoveMatrix);
                            }
                        }
                    }
                }
                if(v_possMoves.isEmpty()){
                    System.out.println("No possible moves for player "+forWho);
                }
                moveMap.put(forWho,v_possMoves);
            }
        }
        return moveMap.get(forWho);
        }

【问题讨论】:

  • 欢迎来到 SO,Maarten!我们感谢您的礼貌,但不鼓励在本网站上进行介绍(以及在较小程度上签名)。这主要是因为它们在问题预览中占用了大量空间。此外,如果您将代码缩进四个空格,则不必同时使用反引号。抛开这些小事不谈,第一次发帖不错!
  • 我不知道 Hashmap 使用什么散列算法,但简单的散列容易发生冲突,小型散列表也是如此。

标签: java hashtable hashmap


【解决方案1】:

编辑:我刚刚想到了一些可能肯定导致问题的东西。你有没有修改Matrix 中的数组?您不得在将键添加到映射后更改键中的相关值 - 这可能会导致误报甚至误报(您可以使用在 put 中使用的相同键引用和例如,如果您进行了影响哈希码的更改,则找不到与 get 的匹配项。


不清楚是什么代码打印了“已识别的 hashCode”部分......但听起来它是 假设 哈希码是唯一的。 不要那样做。它们应该是独一无二的。它们旨在表示可能相等 - 一种使查找内容更快的优化。这个想法是,如果您找到匹配的哈希码,您应该始终检查“真正的”相等性。

您还没有说明数组中的值是什么,但假设它们是 0(空)、1(黑色)和 2(白色),那么它们的可能组合比一个普通的 32 位哈希码可以表示……你有 364 种可能性,而不是 232。现在我敢说其中大部分都不是真正可行的奥赛罗棋盘,但如果Arrays.deepHashCode 试图针对这种情况进行优化,以便为所有有效的奥赛罗棋盘提供唯一的哈希码,我会感到惊讶,即使 是可能的。

附带说明,您的hashCode 方法比它需要的要复杂得多。试试这个:

@Override
public int hashCode() { 
  return Arrays.deepHashCode(board); 
}

【讨论】:

  • 扩展概念,equals()和hashcode()之间的约定是给定o1和o2,o1.hashcode() == o2.hashcode()当且仅当o1.equals( o2) 为真(等于对象具有相同的哈希码)。反之则不成立(相同的哈希码并不意味着相等)。
  • 注意:对于n &gt;= 0 的所有值,new Long((long) i &lt;&lt; 32 ^ i ^ m) 的 hashCode() 与 m 相同(负值更复杂一些),即约 40 亿个 Long 值具有相同的 hashCode ()。 ;)
  • @Soronthar 不,这不是“当且仅当”,而仅仅是“如果”。 o1.equals(o2) 可以为假,即使 o1.hashCode() == o2.hashCode()。
  • 这就是我们需要对 cme​​ts 投反对票的原因。 Soronthar 的评论不正确。
  • @Steve:不只是投反对票,还取消投赞成票。哎呀:)
【解决方案2】:

equals() 的实现中使用Arrays.equals 是不对的。你应该使用Arrays.deepEquals。所以你对 equals() 的实现将结束于:

Matrix other = (Matrix) obj; return Arrays.deepEquals(board, other.board);

http://download.oracle.com/javase/6/docs/api/java/util/Arrays.html

【讨论】:

  • 哎呀,我解决了这个问题。但问题依然存在。
猜你喜欢
  • 2023-01-14
  • 1970-01-01
  • 2021-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-11
  • 2018-12-20
相关资源
最近更新 更多