zoukankan      html  css  js  c++  java
  • 词典的实现(4)-使用Hash方式来实现词典

    1,实现思路

    public class HashedDictionary<K, V> implements DictionaryInterface<K, V>,
            Serializable {

    定义HashedDictionary.java,作为Hash词典的实现,该词典实现了如下功能:

    ①向词典中添加元素 ,②根据查找键从词典中删除元素, ③从词典中获取某个查找键的值, ④实现了词典中查找键Key的迭代器和Value的迭代器

    2,对于Map词典而言,由第一行代码知:它底层实质上是一个数组。

    第三行代码中的locationUsed 用来记录哈希表中某个位置是否被使用。由于冲突处理的需要,当删除某个元素时,只是将该元素作为标记删除而不是真正删除该元素,那么该元素所占用的位置实质上是已经被使用了的。但是,关于这个变量,程序还是有问题的。

    1     private TableEntry<K, V>[] hashTable;
    2     private int numberOfEntries;//hashTable元素个数
    3     private int locationUsed;//记录hashTable 位置的使用
    4     private static final int DEFAULT_SIZE = 101;// 哈希表的默认大小,素数
    5     private static final double MAX_LOAD_FACTOR = 0.5;// 装载因子

    3,构造函数,用来生成一个Hash词典实例,从第六行代码看出,它实质上是 new 一个TableEntry类型的数组,而这个数组就是用来存放<key,value>对的索引。TableEntry是HashedDictionary的一个内部类,正是由它来封装实际的<key,value>元素,而HashedDictionary的对象就代表一个词典,词典的基本操作就是操作TableEntry数组指向的每个<key,value>

    1     public HashedDictionary(int tableSize) {
    2         /*
    3          * 当用来构造哈希表的参数不是素数时,寻找与该参数最接近的下一个素数
    4          */
    5         int primeSize = getNextPrime(tableSize);
    6         hashTable = new TableEntry[primeSize];
    7         numberOfEntries = 0;
    8         locationUsed = 0;
    9     }
    1 private class TableEntry<S, T> implements Serializable {
    2         private S entryKey;
    3         private T entryValue;
    4         private boolean inTable;

    4,再看看迭代器的实现

    这两个内部分别用来实现Key的迭代器和Value的迭代器,只需要实现在Iterator接口中定义好的方法即可,然后在HashedDictionary.java中再定义两个函数用来获得KeyIterator类型和ValueIterator类型的迭代器即可。

    private class KeyIterator implements Iterator<K>{
    ......
    
    private class ValueIterator implements Iterator<V>{
    public Iterator<K> getKeyIterator() {
            return new KeyIterator();
        }
    public Iterator<V> getValueIterator() { return new ValueIterator(); }

    5,哈希函数是如何实现的

    先通过hashCode方法获得查找键的散列码,再将散列码压缩到哈希表的表长范围内

    1     private int getHashIndex(K key){
    2         //先获得散列码,再通过 %(求余运算) 将散列码压缩为索引
    3         int hashIndex = key.hashCode() % hashTable.length;
    4         if(hashIndex < 0)
    5             hashIndex = hashIndex + hashTable.length;
    6         return hashIndex;
    7     }

    6,在添加<key,value>元素时是如何处理冲突的?这里采用了开放定址之线性探测法处理冲突。

    调用probe(index,key)来处理冲突.处理冲突的原理如下:

    removedStateIndex 用来记录哈希表中“第一个已删除元素的索引”---因为采用线性探测处理冲突,比如说有4个元素 a,b,c,d 都映射到第4个索引位置,则a在第4号位置,b在第5号位置,c在第6号位置,d在第7号位置。现假设第5号和第6号元素都被删除了,那么removeStateIndex会标记第5号位置。这样做的原因是:当待插入的新元素被映射到第4号位置时,若后面所有元素的查找键与待插入元素的查找键不同,则将该新元素插入到第5号位置,而不是将它插入到后面的某个值为null的位置(当然,若没有被标记删除的元素,它将会插入到哈希表中第一个值为null的元素)

    处理结果是:若找到了与待插入元素的查找键相同的元素,则返回该元素的索引位置,add方法将更新该索引位置处的值。

    若没有找到与待插入元素的查找键相同的元素,则要么插入到removedStateIndex(值不为-1)标记的索引位置,要么插入到哈希表中第一个值为null的元素索引位置。

    1     public V add(K key, V value) {
    2         V oldValue;
    3         if(isFull())
    4             rehash();
    5         int index = getHashIndex(key);
    6         index = probe(index, key);//线性探测
     1 private int probe(int index, K key){
     2         boolean found = false;
     3         int removedStateIndex = -1;
     4         while(!found && (hashTable[index] != null)){
     5             if(hashTable[index].isIn()){
     6                 if(key.equals(hashTable[index].getEntryKey()))
     7                     found = true;
     8                 else
     9                     index = (index + 1) % hashTable.length;
    10             }
    11             else
    12             {//保存处于已删除状态的第一个位置的索引(查找过程中遇到了被标记删除的元素)
    13                 if(removedStateIndex == -1)
    14                     removedStateIndex = index;
    15                 index = (index + 1) % hashTable.length;//继承探测后面的元素的键是否与查找键相同
    16             }
    17         }//end while
    18         if(found || (removedStateIndex == -1))
    19             return index;
    20         else
    21             return removedStateIndex;
    22     }

    7,由于可能产生冲突,某个Key通过散列函数生成索引之后,该索引位置可能已经存放了其它元素,那它又是如何正确地通过getValue(K key)方法来正确取得key所对应的value?-----locate(index, key)函数的功能。

     因为getValue方法根据Key获得的Value可能是某个被冲突的元素的值,而不是Key所对应的Value,因此需要继承向后比较Key。

    1     public V getValue(K key) {
    2         V result = null;
    3         int index = getHashIndex(key);//获得key对应的哈希索引
    4         index = locate(index, key);//返回处理冲突后的最终的索引地址
    5         
    6         if(index != -1)
    7             result = hashTable[index].getEntryValue();
    8         return result;
    9     }
     1 private int locate(int index, K key){
     2         boolean found = false;
     3         //当找到一个hashTable中为null的元素,表示查找失败
     4         while(!found && (hashTable[index] != null)){
     5             //index 处 的key对应的value没有被标记删除且key 相同时
     6             if(hashTable[index].isIn() && key.equals(hashTable[index].getEntryKey()))
     7                 found = true;
     8             else 
     9                 index = (index + 1) % hashTable.length;
    10         }
    11         int result = -1;
    12         if(found)
    13             result = index;
    14         return result;
    15     }
  • 相关阅读:
    Scala学习笔记——断言和单元测试
    Spark学习笔记——读写Hbase
    Spark学习笔记——读写HDFS
    Scala学习笔记——简化代码、柯里化、继承、特质
    Spark学习笔记——读写MySQL
    Hbase学习笔记——基本CRUD操作
    Spark学习笔记——在集群上运行Spark
    IDEA启动Tomcat服务器时某些端口(如1099端口)被占用的解决办法
    ResultMap和ResultType在使用中的区别、MyBatis中Mapper的返回值类型
    java中的stream的Map收集器操作
  • 原文地址:https://www.cnblogs.com/hapjin/p/4611624.html
Copyright © 2011-2022 走看看