一、概念

      散列(hashing)表(HashTable)是一个具有固定大小且包含一些数据的数组,其insert, remove, contains操作的复杂度都在常数级别。

      HashTable存储的数据类型必须提供适当的equals与HashCode方法,用于查找与更新操作。

      例:该类可以作为HashTable存储的数据类型

import java.util.*;

public class HashModel {
    public boolean equals(Object rhs) {
        return rhs instanceof HashModel && name.equals(((HashModel) rhs).name);
    }
    
    /**
     * String的HashCode()与如下方法类似,String对象中持有一个hash变量用于存储其hash值,只有第一次调用hashCode的时候需要进行计算。
     * 由于计算很复杂会影响效率,利用hash变量避免每次调用hashCode都要进行计算,这种方法叫做闪存散列代码。这种方法主要用于不可变对象。
     * 
     * public static int hashCode() {
     *     if(hash != 0)
     *         return hash;
     *     for(int i = 0; i < str.length(); i++)
     *         hash = 37 * i + str.charAt(i);
     *     return hash;
     * }
     */
    public int hashCode() {
        return name.hashCode();
    }
    
    private String name;
    private double salary;
    private int seniority;
    Hashtable<String, Double> hashTable;
    
}

 

 

二、原理

      HashTable通过一个Hash函数将数据存入其中,一个好的HashTable应当使数据均匀的分布到数组之中,即不同数据传入Hash函数得到的结果不会出现太多重复。

      比较好的办法是把HashTable中数组的大小设定为一个素数,并且Hash函数的计算方式也需要保证传入不同数据得到的Hash值尽量不同。当出现不同数据通过Hash函数计算得到的结果相同时,这种情况成为冲突(collision)。

      例:一个比较好的Hash函数

public static int hash(String key, int tableSize) {
    int hashVal = 0;
    
    for(int i = 0; i < key.length(); i++) 
        hashVal = 37 * hashVal + key.charAt(i);
    
    hashVal %= tableSize;
    //因为该计算方式hashVal可能溢出
    if(hashVal < 0) 
        hashVal += tableSize;
    
    return hashVal;
}

 

三、分离链接法实现HashTable

      该方法在HashTable数组每个单元格存放一个List,当发生冲突时直接操作对应List。

      实现如下(同时支持HashTable的扩容):

import java.util.*;
/**
 * 分离链接哈希表
 * @author Athrun
 */
public class SeparateChainingHashTable<E> {
    public SeparateChainingHashTable() {
        this(DEFAULT_TABLE_SIZE);
    }
    
    public SeparateChainingHashTable(int size) {
        this.theLists = new LinkedList[nextPrime(size)];
        for(int i = 0; i < theLists.length; i++)
            theLists[i] = new LinkedList<E>();
    }
    
    private static final int DEFAULT_TABLE_SIZE = 101;
    private List<E>[] theLists;
    private int currentSize;
    
    private static int nextPrime(int n) {
        if(n % 2 == 0)
            n++;
        for( ; !isPrime(n); n += 2)
            ;
        return n;
    }
    private static boolean isPrime(int n) {
        if(n == 2 || n == 3)
            return true;
        if(n == 1 || n % 2 == 0) 
            return false;
        for(int i = 3; i * i <= n; i += 2)
            if(n % i == 0)
                return false;
        return true;
    }
    
    private int myhash(E x) {
        int hashVal = x.hashCode();
        hashVal %= theLists.length;
        if(hashVal < 0)
            hashVal += theLists.length;
        return hashVal;
    }
    private void rehash() {
        List<E>[] oldLists = theLists;
        
        theLists = new List[nextPrime(2 * theLists.length)];
        for(int i = 0; i < theLists.length; i++)
            theLists[i] = new LinkedList<E>();
        currentSize = 0;
        for(int j = 0; j < oldLists.length; j++)
            for(E item : oldLists[j])
                insert(item);
    }
    public void insert(E x) {
        List<E> selectedList = theLists[myhash(x)];
        if(!selectedList.contains(x)) {
            selectedList.add(x);
            if(++currentSize > theLists.length)
                rehash();
        }
    }
    public void remove(E x) {
        List<E> selectedList = theLists[myhash(x)];
        if(selectedList.contains(x)) {
            selectedList.remove(x);
            currentSize--;
        }
    }
    public boolean contains(E x) {
        List<E> selectedList = theLists[myhash(x)];
        return selectedList.contains(x);
    }
    public void makeEmpty() {
        for(int i = 0; i < theLists.length; i++)
            theLists[i].clear();
        currentSize = 0;
    }

}