Java HashMap的實現

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;
	}
}


使用chaining方法的HashMap實現如下:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章