一、概念
散列(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; } }