【问题标题】:Questions about implementing my own HashMap in Java关于在 Java 中实现我自己的 HashMap 的问题
【发布时间】:2013-01-28 18:26:40
【问题描述】:

我正在执行一项任务,我必须实现自己的 HashMap。在赋值文本中,它被描述为一个列表数组,无论何时你想添加一个元素,它在数组中的最终位置是由它的 hashCode 决定的。在我的情况下,它是电子表格中的位置,所以我刚刚取了 columnNumber + rowNumber,然后将其转换为 String,然后转换为 int,作为 hashCode,然后我将它插入到数组中的那个位置。当然是以Node(key, value)的形式插入的,其中key是cell的位置,value是cell的值。

但是我必须说我不明白为什么我们需要一个列表数组,因为如果我们最终得到一个包含多个元素的列表,它不会大大增加查找时间吗?那么它不应该是一个节点数组吗?

我还发现了这个用 Java 实现的 HashMap:

public class HashEntry {
      private int key;
      private int value;

      HashEntry(int key, int value) {
            this.key = key;
            this.value = value;
      }     

      public int getKey() {
            return key;
      }

      public int getValue() {
            return value;
      }
}

public class HashMap {
  private final static int TABLE_SIZE = 128;

  HashEntry[] table;

  HashMap() {
        table = new HashEntry[TABLE_SIZE];
        for (int i = 0; i < TABLE_SIZE; i++)
              table[i] = null;
  }

  public int get(int key) {
        int hash = (key % TABLE_SIZE);
        while (table[hash] != null && table[hash].getKey() != key)
              hash = (hash + 1) % TABLE_SIZE;
        if (table[hash] == null)
              return -1;
        else
              return table[hash].getValue();
  }

  public void put(int key, int value) {
        int hash = (key % TABLE_SIZE);
        while (table[hash] != null && table[hash].getKey() != key)
              hash = (hash + 1) % TABLE_SIZE;
        table[hash] = new HashEntry(key, value);
  }
}

那么 put 方法是否正确,首先查看 table[hash],如果它不为空并且如果那里的内容没有得到密钥,则在 put 方法中输入,然后继续到表 [(hash + 1) % TABLE_SIZE]。但如果它是同一个键,它只会覆盖该值。那这样理解正确吗?是不是因为 get 和 put 方法使用相同的方法在 Array 中查找位置,给定相同的键,它们最终会在 Array 中的相同位置结束?

我知道这些问题可能有点基本,但我花了相当长的时间试图解决这个问题,为什么任何帮助都将不胜感激!

编辑

所以现在我尝试通过 Node 类自己实现 HashMap,它只是 用一个键和一个对应的值构造一个节点,它还有一个 getHashCode 方法,在这里我只是将两个值连接起来。

我还构建了一个 SinglyLinkedList(之前分配的一部分),我将其用作存储桶。

而我的哈希函数就是 hashCode % hashMap.length。

这是我自己的实现,你觉得呢?

package spreadsheet; 

public class HashTableMap {

  private SinglyLinkedListMap[] hashArray;
  private int size;


  public HashTableMap() {
    hashArray = new SinglyLinkedListMap[64];
    size = 0;  
  }


  public void insert(final Position key, final Expression value) {

      Node node = new Node(key, value); 
      int hashNumber = node.getHashCode() % hashArray.length;       
      SinglyLinkedListMap bucket = new SinglyLinkedListMap();
      bucket.insert(key, value);
      if(hashArray[hashNumber] == null) {
        hashArray[hashNumber] = bucket;
        size++; 
      }
      if(hashArray[hashNumber] != null) {
        SinglyLinkedListMap bucket2 = hashArray[hashNumber];
        bucket2.insert(key, value);
        hashArray[hashNumber] = bucket2;
        size++; 
      }
      if (hashArray.length == size) {
          SinglyLinkedListMap[] newhashArray = new SinglyLinkedListMap[size * 2];
      for (int i = 0; i < size; i++) {
          newhashArray[i] = hashArray[i];
      }
      hashArray = newhashArray;
    }
  } 

  public Expression lookUp(final Position key) {
      Node node = new Node(key, null); 
      int hashNumber = node.getHashCode() % hashArray.length;
      SinglyLinkedListMap foundBucket = hashArray[hashNumber];
      return foundBucket.lookUp(key); 
  }
 }

查找时间应该在 O(1) 左右,所以我想知道是不是这样?如果没有,在这方面我该如何改进?

【问题讨论】:

  • 碰撞/性能问题在*文章中得到解决。
  • 他们谈论哈希冲突,但他也有密钥冲突,如果不更改密钥编码,这将无法工作。
  • 谁给了我负面评价,为什么?

标签: java hashmap


【解决方案1】:

你必须有一些计划来处理哈希冲突,其中两个不同的键落在同一个桶中,你数组的同一个元素。

最简单的解决方案之一是为每个存储桶保留一个条目列表。

如果你有一个好的散列算法,并确保桶的数量大于元素的数量,你应该最终得到大多数桶有零个或一个项目,所以列表搜索不应该花很长时间。如果列表变得太长,是时候用更多的桶重新散列以分散数据了。

【讨论】:

    【解决方案2】:

    这真的取决于你的哈希码方法有多好。假设你试图让它尽可能糟糕:你让哈希码每次都返回 1。如果是这种情况,您将拥有一个列表数组,但该数组中只有 1 个元素包含任何数据。该元素只会增长到包含一个巨大的列表。

    如果你这样做了,你会得到一个非常低效的哈希图。但是,如果您的哈希码稍微好一点,它会将对象分配到许多不同的数组元素中,因此效率会更高。

    最理想的情况(通常是无法实现的)是有一个哈希码方法,无论您放入什么对象,它都会返回一个唯一的数字。如果你能做到这一点,你将永远不需要列表数组。你可以只使用一个数组。但是由于您的哈希码不是“完美的”,因此两个不同的对象可能具有相同的哈希码。您需要能够通过将它们放在同一数组元素的列表中来处理这种情况。

    但是,如果您的哈希码方法“非常好”并且很少发生冲突,那么列表中的元素很少会超过 1 个。

    【讨论】:

      【解决方案3】:

      Lists 通常被称为存储桶,是一种处理冲突的方法。当两个数据元素具有相同的哈希码 mod TABLE SIZE 时,它们会发生冲突,但两者都必须存储。

      更糟糕的冲突是两个不同的数据点具有相同的key——这在哈希表中是不允许的,一个会覆盖其他的。如果您只是将行添加到列,那么 (2,1) 和 (1,2) 的键都是 3,这意味着它们不能存储在同一个哈希表中。如果您在没有分隔符的情况下将字符串连接在一起,那么问题在于 (12,1) 与 (1, 21) --- 两者都有键“121” 使用分隔符(例如逗号)所有键都将是不同的。

      如果哈希码是相同的 mod TABLE_SIZE,则不同的键可以放在同一个块中。这些列表是将两个值存储在同一个存储桶中的一种方式。

      【讨论】:

      • 嗨,我已经在我的原始帖子中包含了我对 HashMap 的实现,所以如果有人愿意看一下,我会非常高兴:) 查找时间应该在 O(1 ),所以我想知道是不是这样?如果不是,在这方面我该如何改进它?
      【解决方案4】:
      class SpreadSheetPosition {
          int column;
          int row;
      
          @Override
          public int hashCode() {
              return column + row;
          }
      }
      
      class HashMap {
          private Liat[] buckets = new List[N];
      
          public void put(Object key, Object value) {
              int keyHashCode = key.hashCode();
              int bucketIndex = keyHashCode % N;
              ...
          }
      }
      

      比较有 N 个列表和只有一个列表/数组。为了在列表中搜索,必须遍历整个列表。通过使用列表数组,至少可以减少单个列表。甚至可能获取一个或零个元素的列表(null)。

      如果hashCode() 尽可能独特,则立即找到的机会很高。

      【讨论】: