一.哈希表的相关概念
哈希函数的构造方法:
1. 直接定址法
2. 数字分析法
3. 平法取中法
4. 折叠法
5. 除留余数法
6. 随机数法
二.处理冲突的方法
1. 开放定制法(线性探测再散列、二次探测再散列、伪随机探测再散列)
2. 再哈希法
3. 链地址法
4. 建立一个公共溢出区
三.MyHashMap的实现
要点:哈希函数的方法选用“除留余数法”,处理冲突的方法是用“链地址法”,也即是当发生冲突时,凡是哈希地址为i的记录,都插入到头指针为tables[i]的链表中,插入的位置可以是表头,也可以是表尾,也可以在中间(以保持key的有序)。
本次实现的MyHashMap是将其插入表头。
package edu.njupt.zhb;
/*
*@author: ZhengHaibo
*web: http://blog.youkuaiyun.com/nuptboyzhb
*mail: zhb931706659@126.com
*2014-3-25 Nanjing,njupt,China
*/
public class MyHashMap<K,V>{
/**
* HashMap数组中的节点
* @param <K>
* @param <V>
*/
public class MyEntry<K,V>{
public K key;//键
public V value;//值
public MyEntry<K,V> next;//下一个结点
}
private MyEntry<K, V> []tables;
private final int INIT_LEN=50;
public MyHashMap() {
tables=new MyEntry[INIT_LEN];
}
/**
* 哈希函数的构造方法:除留余数法
* @param hashcode
* @return
*/
public int indexFor(int hashcode){
return Math.abs(hashcode)%tables.length;
}
/**
* 获取键为key的值
* @param key
* @return
*/
public V get(K key){
if(key==null){
return null;
}
int hashcode=key.hashCode();//计算键的哈希值
int index=indexFor(hashcode);//根据哈希值计算其在表中的下标index
MyEntry<K, V> pNode=tables[index];//取出第一个节点,也即是链表的头
//在链表中查找
while(pNode!=null){//对链表进行遍历
//当key的哈希值和真实值相等时返回
if(pNode.key.hashCode()==hashcode&&(key==pNode.key || key.equals(pNode.key))){
return pNode.value;
}
pNode=pNode.next;//链表:遍历下一个值
}
return null;
}
/**
* 将键值对放入map当中
* 解决冲突的方法:链地址法
* @param key
* @param value
* @return
*/
public V put(K key,V value){
if(key==null){
return null;
}
int hashcode=key.hashCode();
int index=indexFor(hashcode);
MyEntry<K, V> head=tables[index];//取出第一个节点,也即是链表的头
MyEntry<K, V> pNode=head;
//首先查找是否已经存在key
while(pNode!=null){
//当key的哈希值和真实值相等时
if(pNode.key.hashCode()==hashcode&&(key==pNode.key || key.equals(pNode.key))){
pNode.value=value;//更新当前key的值
return pNode.value;
}
pNode=pNode.next;//链表:遍历下一个值
}
//如果没有查找到,将该值插入到链表的表头
MyEntry<K, V> newNode=new MyEntry<K,V>();
newNode.key=key;
newNode.value=value;
newNode.next=head;
tables[index]=newNode;
return value;
}
/**
* 根据key删除对应的值
* 思路:先查找,后删除
* @param key
* @return
*/
public V remove(K key){
if(key==null){
return null;
}
int hashcode=key.hashCode();
int index=indexFor(hashcode);
MyEntry<K, V> head=tables[index];
MyEntry<K, V> pNode=head;
if(pNode==null){
return null;
}
//表头
if(pNode.key.hashCode()==hashcode&&(key==pNode.key || key.equals(pNode.key))){
V value=pNode.value;
tables[index]=pNode.next;//删除头节点
return value;
}
//首先查找是否已经存在key
while(pNode.next!=null){
//当key的哈希值和真实值相等时
if(pNode.next.key.hashCode()==hashcode&&(key==pNode.next.key || key.equals(pNode.next.key))){
V value=pNode.value;
pNode.next=pNode.next.next;//删除该节点
return value;
}
pNode=pNode.next;//链表:遍历下一个值
}
return null;
}
public void print(){
for(int i=0;i<tables.length;i++){
MyEntry<K, V> entry=tables[i];
if(entry!=null){
while(entry!=null){
System.out.println("key="+entry.key+",value="+entry.value);
entry=entry.next;
}
}
}
}
/**
* 测试
* @param args
*/
public static void main(String[] args) {
MyHashMap<String, String> map=new MyHashMap<String, String>();
for(int i=0;i<100;i++){
map.put("stu"+i, ""+i);
}
for(int i=0;i<20;i++){
map.remove("stu"+i);
}
map.print();
}
}
四.MyHashSet的实现
思路:和HashMap的思路一样,我们也是先计算hash值,然后折算出位置index,然后进行插入或删除。查找元素的效率很高,不需要对所有元素进行比较。
package edu.njupt.zhb;
/*
*@author: ZhengHaibo
*web: http://blog.youkuaiyun.com/nuptboyzhb
*mail: zhb931706659@126.com
*2014-3-25 Nanjing,njupt,China
*/
public class MyHashSet<T>{
/**
* 节点
*/
public class MyEntry<T>{
public T data;//当前数据
public MyEntry<T> next;
}
private MyEntry<T> []tables;//数组
private final int INIT_LEN=50;
private int size;
public MyHashSet(){
tables=new MyEntry[INIT_LEN];
size=0;
}
/**
* 哈希函数构造方法
* @param hashcode 哈希值
* @return 对应的数组下标
*/
private int indexFor(int hashcode){
return Math.abs(hashcode)%tables.length;
}
/**
* 添加元素
* @param object
* @return
*/
public boolean add(T data){
if(data==null){
return false;
}
int hashcode=data.hashCode();//计算哈希值
int index=indexFor(hashcode);//定位存储的位置
MyEntry<T> head=tables[index];//取出该位置的第一个元素(链表的头)
MyEntry<T> pNode=head;//查找链表中是否存在
while(pNode!=null){
if(pNode.data.hashCode()==hashcode&&(pNode.data==data||pNode.data.equals(data))){
return false;//已经存在重复的元素
}
pNode=pNode.next;
}
//不存在重复的元素或该位置为空,将该节点添加到链表的头
MyEntry<T> newNode=new MyEntry<T>();
newNode.data=data;
newNode.next=head;
tables[index]=newNode;
size++;
return true;
}
public boolean remove(T data){
if(data==null){
return false;
}
int hashcode=data.hashCode();
int index=indexFor(hashcode);
MyEntry<T> head=tables[index];
MyEntry<T> pNode=head;
if(pNode.data.hashCode()==hashcode&&(pNode.data==data||pNode.data.equals(data))){
tables[index]=pNode.next;
size--;
return true;
}
while(pNode.next!=null){
if(pNode.next.data.hashCode()==hashcode&&(pNode.next.data==data||pNode.next.data.equals(data))){
pNode.next=pNode.next.next;//删除该元素
size--;
return true;
}
pNode=pNode.next;
}
return false;
}
public void print(){
for(int i=0;i<tables.length;i++){
MyEntry<T> entry=tables[i];
if(entry!=null){
while(entry!=null){
System.out.println(entry.data);
entry=entry.next;
}
}
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
MyHashSet<String> set=new MyHashSet<String>();
for(int i=0;i<100;i++){
set.add(i+"");
}
for(int i=0;i<10;i++){
set.remove(i+"");
}
System.out.println(set.size);
set.print();
}
}
附录:
用链地址法处理冲突时的Hash表结构示意图如下:
相关博客:
[1].HashMap的实现原理:http://zhangshixi.iteye.com/blog/672697
[2].LinkedHashMap的实现原理: http://zhangshixi.iteye.com/blog/673789