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