HashMap是Java中經常使用的集合類。HashMap的每個元素是一個<Key,Value>鍵值對,在內部用數組來保存每個元素,hash函數將Key作爲參數,計算出Value的存儲位置,即數組的下標。實現HashMap,關鍵在於找到一個hash函數,使其儘量少的發生衝突。那麼,何爲衝突?如下圖所示,
當值“F"插入HashMap集合時,發現他所對應的位置已有數據插入,此時便發生了衝突。解決衝突的辦法這裏列舉三種:linear probing, double hashing, 和chaining。
linear probing方法
linear probing解決衝突的方式是:當衝突發生,後來的數據依次往後尋找空閒位置,一旦找到,便插入。如下圖所示:
當值"F"插入時,因爲原有位置已被"D"佔領,則依次往後查找,在值"C"之後發現空閒位置並插入。查找到數組的尾部時,返回到頭部繼續查找,如下圖所示:
如果查找到本應輸入的位置,依舊沒有可插入的位置,說明hashmap存滿,如下圖所示:
類HashMapNode表示HashMap中的每個元素,它主要有兩個成員:Key和Value。實現如下:
public class HashMapNode {
private Object key;
private Object value;
// construction
public HashMapNode(Object key, Object value){
this.key = key;
this.value = value;
}
// get methods
public Object getKey(){
return key;
}
public Object getValue(){
return value;
}
// set method
public void setValue(Object newValue){
this.value = newValue;
}
}
類HashMap是採用linear probing方式的hashmap實現,具體如下:
import java.util.ArrayList;
import java.util.List;
public class HashMap<K, V> {
private int multiplier;
private int modulus;
private int hashMapSize;//HashMap容量
private HashMapNode[] array;
private int size = 0;//HashMap現有元素個數
// construction
// construct a HashMap with 4000 places and given hash paarameters
public HashMap(int multiplier, int modulus){
this.multiplier = multiplier;
this.modulus = modulus;
this.hashMapSize = 4000;
this.array = new HashMapNode[this.hashMapSize];
}
// construct a HashMap with given capacity and given hash parameters
public HashMap(int hashMapSize, int multiplier, int modulus){
this.multiplier = multiplier;
this.modulus = modulus;
this.hashMapSize = hashMapSize;
this.array = new HashMapNode[this.hashMapSize];
}
// hashing
public int hash(K key){
return Math.abs(multiplier * key.hashCode()) % modulus % this.hashMapSize;
}
// size
public int size(){
return size;
}
// return the number of nodes currently stored in the map
public boolean isEmpty(){
return size == 0;
}
// interface methods
@SuppressWarnings("unchecked")
public List<K> keys(){
List<K> list = new ArrayList<K>();
for(int i = 0; i < this.hashMapSize; i++){
if(array[i] != null){
list.add((K)array[i].getKey());
}
}
return list;
}
@SuppressWarnings("unchecked")
public V put(K key, V value){
int hashCode = hash(key);//hashCode是value本應存放的位置
if(array[hashCode] == null){//該位置是空的,則直接放入,不需要處理衝突
HashMapNode node = new HashMapNode(key,value);
array[hashCode] = node;
size++;
return null;
}else if(key.equals((K)array[hashCode].getKey())){//該主鍵已存在,則更新對應的值
V oldValue = (V)array[hashCode].getValue();
array[hashCode].setValue(value);
return oldValue;
}
int probe = (hashCode + 1) % this.hashMapSize;//開始處理衝突
while(probe != hashCode){
if(array[probe] == null){//找到空閒位置
HashMapNode node = new HashMapNode(key,value);
array[probe] = node;
size++;
return null;
}else if(key.equals((K)array[probe].getKey())){//該主鍵已存在,則更新對應的值
V oldValue = (V)array[probe].getValue();
array[probe].setValue(value);
return oldValue;
}else{
probe = (probe + 1) % this.hashMapSize;
}
}
return null;
}
@SuppressWarnings("unchecked")
public V get(K key){
int hashCode = hash(key);
if(array[hashCode] != null && key.equals((K)array[hashCode].getKey())){
return (V)array[hashCode].getValue();
}
int probe = (hashCode + 1) % this.hashMapSize;
while(probe != hashCode){//依次往後查找,直到回到原點
if(array[probe] == null){
probe = (probe + 1) % this.hashMapSize;
continue;
}else if(key.equals((K)array[probe].getKey())){
return (V)array[probe].getValue();
}else{
probe = (probe + 1) % this.hashMapSize;
}
}
return null;
}
@SuppressWarnings("unchecked")
public V remove(K key){
int hashCode = hash(key);
if(array[hashCode] != null && key.equals((K)array[hashCode].getKey())){
V oldValue = (V)array[hashCode].getValue();
array[hashCode] = null;//該位置設置爲null,表示被刪除
size--;
return oldValue;
}
int probe = (hashCode + 1) % this.hashMapSize;
while(probe != hashCode){
if(array[probe] == null){
probe = (probe + 1) % this.hashMapSize;
continue;
}else if(key.equals((K)array[probe].getKey())){
V oldValue = (V)array[probe].getValue();
array[probe] = null;
size--;
return oldValue;
}else{
probe = (probe + 1) % this.hashMapSize;
}
}
return null;
}
}
double hashing 方法
double hashing方法,思想是當發生衝突時,結合第二個hash函數,新生成一個hashCode,直到不發生衝突爲止。此處使用的第二個hash函數如下:secondaryModulus (abs(hashCode(key)) mod secondaryModulus),計算元素下標值的方法如下:hash(key) + j * secondary(key)。j從0開始逐漸遞增,直到不衝突爲止。具體實現如下:
import java.util.ArrayList;
import java.util.List;
public class DoubleHashMap<K, V> {
private int multiplier;
private int modulus;
private int secondaryModulus;
private int hashMapSize;
private HashMapNode[] array;
private int size = 0;
// construction
// construct a HashMap with 4000 places and given hash paarameters
public DoubleHashMap(int multiplier, int modulus, int secondaryModulus){
this.multiplier = multiplier;
this.modulus = modulus;
this.secondaryModulus = secondaryModulus;
this.hashMapSize = 4000;
this.array = new HashMapNode[this.hashMapSize];
}
// construct a HashMap with given capacity and given hash parameters
public DoubleHashMap(int hashMapSize, int multiplier, int modulus, int secondaryModulus){
this.multiplier = multiplier;
this.modulus = modulus;
this.secondaryModulus = secondaryModulus;
this.hashMapSize = hashMapSize;
this.array = new HashMapNode[this.hashMapSize];
}
// hashing
public int hash(K key){
return Math.abs(multiplier * key.hashCode()) % modulus;
}
public int secondaryHash(K key){
return this.secondaryModulus - Math.abs(key.hashCode()) % this.secondaryModulus;
}
// size
public int size(){
return size;
}
// return the number of nodes currently stored in the map
public boolean isEmpty(){
return size == 0;
}
// interface methods
@SuppressWarnings("unchecked")
public List<K> keys(){
List<K> list = new ArrayList<K>();
for(int i = 0; i < this.hashMapSize; i++){
if(array[i] != null){
list.add((K)array[i].getKey());
}
}
return list;
}
@SuppressWarnings("unchecked")
public V put(K key, V value){
int hashCode = hash(key);//calculating the hash code
if(array[hashCode % this.hashMapSize] == null){//if the hash item is empty, add the data straight away
HashMapNode node = new HashMapNode(key,value);
array[hashCode % this.hashMapSize] = node;
size++;
return null;
}else if(key.equals((K)array[hashCode % this.hashMapSize].getKey())){
V oldValue = (V)array[hashCode % this.hashMapSize].getValue();
array[hashCode % this.hashMapSize].setValue(value);
return oldValue;
}
int secondHashCode = secondaryHash(key);
int probe = 0;//variable to store probing location
int j = 0;
V retValue = null;
while(true){
j++;
probe = (hashCode + j * secondHashCode) % this.hashMapSize;
if(array[probe] == null){
HashMapNode node = new HashMapNode(key,value);
array[probe] = node;
size++;
break;
}else if(key.equals((K)array[probe].getKey())){
retValue = (V)array[probe].getValue();
array[probe].setValue(value);
break;
}
}
return retValue;
}
@SuppressWarnings("unchecked")
public V get(K key){
int hashCode = hash(key) % this.hashMapSize;
if(array[hashCode] != null && key.equals((K)array[hashCode].getKey())){
return (V)array[hashCode].getValue();
}
int probe = (hashCode + 1) % this.hashMapSize;
while(probe != hashCode){
if(array[probe] == null){
probe = (probe + 1) % this.hashMapSize;
continue;
}else if(key.equals((K)array[probe].getKey())){
return (V)array[probe].getValue();
}else{
probe = (probe + 1) % this.hashMapSize;
}
}
return null;
}
@SuppressWarnings("unchecked")
public V remove(K key){
int hashCode = hash(key) % this.hashMapSize;
if(array[hashCode] != null && key.equals((K)array[hashCode].getKey())){
V oldValue = (V)array[hashCode].getValue();
array[hashCode] = null;
size--;
return oldValue;
}
int probe = (hashCode + 1) % this.hashMapSize;
while(probe != hashCode){
if(array[probe] == null){
probe = (probe + 1) % this.hashMapSize;
continue;
}else if(key.equals((K)array[probe].getKey())){
V oldValue = (V)array[probe].getValue();
array[probe] = null;
size--;
return oldValue;
}else{
probe = (probe + 1) % this.hashMapSize;
}
}
return null;
}
}
chaining 方法
chaining方法的核心思想是,當發生衝突時,將衝突的元素用鏈表的形式連接起來,如下圖所示:
ChainingHashMap類表示每個元素,具體實現如下:
public class ChainingHashMapNode {
private Object key;
private Object value;
private ChainingHashMapNode next;
// construction
public ChainingHashMapNode(Object key, Object value){
this.key = key;
this.value = value;
this.next = null;
}
// get methods
public Object getKey(){
return key;
}
public Object getValue(){
return value;
}
public ChainingHashMapNode getNext(){
return next;
}
// set methods
public void setValue(Object newValue){
this.value = newValue;
}
public void setNext(ChainingHashMapNode next){
this.next = next;
}
}
import java.util.ArrayList;
import java.util.List;
public class ChainingHashMap<K, V> {
private int multiplier;
private int modulus;
private ChainingHashMapNode[] array;
private int hashMapSize;
private int size;
// construction
// construct a HashMap with 4000 places and given hash parameters
public ChainingHashMap(int multiplier, int modulus){
this.multiplier = multiplier;
this.modulus = modulus;
this.hashMapSize = 4000;
this.array = new ChainingHashMapNode[this.hashMapSize];
this.size = 0;
}
// construct a HashMap with given capacity and given hash parameters
// hashing
public ChainingHashMap(int hashMapSize, int multiplier, int modulus){
this.multiplier = multiplier;
this.modulus = modulus;
this.hashMapSize = hashMapSize;
this.array = new ChainingHashMapNode[this.hashMapSize];
this.size = 0;
}
public int hash(K key){
return Math.abs(multiplier * key.hashCode()) % modulus % this.hashMapSize;
}
// size
public int size(){
return size;
}
// return the number of nodes currently stored in the map
public boolean isEmpty(){
return size == 0;
}
// interface
@SuppressWarnings("unchecked")
public List<K> keys(){
List<K> list = new ArrayList<K>();
for(int i = 0; i < this.hashMapSize; i++){
ChainingHashMapNode node = array[i];
while(node != null){
list.add((K)node.getKey());
node = node.getNext();
}
}
return list;
}
@SuppressWarnings("unchecked")
public V put(K key, V value){
int hashCode = hash(key);
if(array[hashCode] == null){//沒有發生衝突,放入首位
ChainingHashMapNode node = new ChainingHashMapNode(key,value);
array[hashCode] = node;
size++;
return null;
}
ChainingHashMapNode node = array[hashCode];
ChainingHashMapNode pre = node;
while(node != null){
if(key.equals((K)node.getKey())){//已存在Key,更新值
V oldValue = (V)node.getValue();
node.setValue(value);
return oldValue;
}else{
pre = node;
node = node.getNext();
}
}
ChainingHashMapNode newNode = new ChainingHashMapNode(key,value); //在鏈表末尾添加衝突的元素
pre.setNext(newNode);
size++;
return null;
}
@SuppressWarnings("unchecked")
public V get(K key){
int hashCode = hash(key);
ChainingHashMapNode node = array[hashCode];
while(node != null){
if(key.equals((K)node.getKey())){
return (V)node.getValue();
}else{
node = node.getNext();
}
}
return null;
}
@SuppressWarnings("unchecked")
public V remove(K key){
int hashCode = hash(key);
ChainingHashMapNode node = array[hashCode];
if(node != null && key.equals((K)node.getKey())){
array[hashCode] = node.getNext();
size--;
return (V)node.getValue();
}
ChainingHashMapNode pre = node;
node = node.getNext();
while(node != null){
if(key.equals((K)node.getKey())){
pre.setNext(node.getNext());
size--;
return (V)node.getValue();
}else{
pre = node;
node = node.getNext();
}
}
return null;
}
}
參考:http://www.cs.rmit.edu.au/online/blackboard/chapter/05/documents/contribute/chapter/05/linear-probing.html