手工实现HashMap

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_40301026/article/details/86660646

 

HashMap是Map接口的实现类。

它的底层实现是:采用了哈希表。即:数组+单链表。

类似这样的:

                                     

写在前面:

1.HashMap中的主要变量:

                       Node<k,v>[] table;  //桶数组  ,Node<K,V>类型的数组,里面的元素是链表,用于存放HashMap元素的实体
                       size;          //记录键值对个数
                       loadFactor ;  //负载因子
                       threshold ; //阈值,决定了HashMap何时扩容


public class Node<k,v> {
	public int hash;  //哈希值
	public k key;     //key值
	public v value;   // value值
	Node<k,v> next;   //指向下一个节点
}

2.主要的方法原理:

     (1)构造方法。在JDK源码中当我们自定义HashMap初始容量大小时,构造函数并非直接把我们定义的数值当做HashMap容量大小,而是把该数值当做参数调用方法tableSizeFor,然后把返回值作为HashMap的初始容量大小。自己手写的则省略

static final int tableSizeFor(int cap) {  
    int n = cap - 1;  
    n |= n >>> 1;  
    n |= n >>> 2;  
    n |= n >>> 4;  
    n |= n >>> 8;  
    n |= n >>> 16;  
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;  
}  

 此代码看到一篇博客讲解的比较好:https://blog.csdn.net/fan2012huan/article/details/51097331

   (2)put方法。主要来存储数据元素到数组中。主要流程为:

      

在存储中还要考虑到扩容问题:

随着HashMap中元素的数量越来越多,发生碰撞的概率就越来越大,所产生的链表长度就会越来越长,这样势必会影响HashMap的速度,为了保证HashMap的效率,系统必须要在某个临界点进行扩容处理。
 所谓的扩容就是,重新构建一个新的数组,遍历旧的数组取出所有元素,再装到新的数组中去。(!!重新计算每个元素在数组中的位置再装到新数组中去。即:重新计算每个数组哈希码(因为数组长度变了,哈希码也变了。))

扫描二维码关注公众号,回复: 5288957 查看本文章
static int indexFor(int h, int length) { return h & (length-1);}

  JDK 1.6 当数量大于容量 * 负载因子即会扩充容量。(使用此方法)
  JDK 1.7 初次扩充为:当数量大于容量时扩充;第二次及以后为:当数量大于容量 * 负载因子时扩充。
  JDK 1.8 初次扩充为:与负载因子无关;第二次及以后为:与负载因子有关。其详细计算过程需要具体详解。原来旧的数组复制进去。

(3)get方法和put的方法比较类似。 获得外界传入的key值后,再通过算法得到哈希码,然后就去桶数组找就行了。

  (4)其他方法原理相对比较简单省略。

附上自己写的源码:

package MyhashSet;

public class Node<k,v> {
	public int hash;  //哈希值
	public k key;   //key值
	public v value;   // value值
	Node<k,v> next;             //指向下一个节点
	
	
	public Node(int hash, k key, v value) {
		super();
		this.hash = hash;
		this.key = key;
		this.value = value;
	}
}
package MyhashSet;

public class MHashMap<k,v> {
	private Node<k,v>[] table;  //桶数组
	private int size;			//记录键值对个数
	private double loadFactor ;  //负载因子
	private int threshold ; //阈值
	
	//构造方法
	
	//1.构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。 
	public MHashMap() {
		this(16,0.75);
	}
	//2.构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。 
	public MHashMap(int capacity) {  
		this.table = new Node[capacity];
		this.loadFactor = 0.75;
	}
	//3.构造一个带指定初始容量和加载因子的空 HashMap。
	public MHashMap(int capacity,double loadFactor) {
		this.table = new Node[capacity];
		this.loadFactor  = loadFactor;
	}
	
	//从此映射中移除所有映射关系。
	public void clear() {
		if(table!=null && size > 0) {
			for(int i = 0;i < table.length;i++) {
				table[i] = null;
				size = 0;
			}
		}
	}
	
	//由哈希码得到哈希值
	public int myhash(int m,int length){
		return m&(length-1); //直接位运算
	}
	
	//返回指定键所映射的值;如果对于该键来说,此映射不包含任何映射关系,则返回 null。
	public v get(k key) {
       int hash = myhash(key.hashCode(),table.length); //由key值获得哈希值
       Node newnode = table[hash];
       v vaule = null;
       while(newnode!=null) {
    	   if(newnode.key.equals(key)) {
    		   vaule = (v) newnode.value;
    		   break;
    	   } 
    	   else {
    		   newnode = newnode.next;
    	   }
       }
       return vaule;
    }
	
	//存储。如果该映射以前包含了一个该键的映射关系,则旧值被替换。 
	public void put(k key,v value) {
		int hash = myhash(key.hashCode(),table.length);
		Node<k,v> newnode = new Node(hash,key,value) ;//新节点初始化
		Node temp = table[hash]; //相当于遍历指针
		boolean judge = false;//用来记录key值是不是重复了
		Node temporary = null;//临时用来保存最后一个结点
		
		resize();  //数组扩容检测
		
		if(temp == null)
		{
			table[hash] = newnode;
			size++;
		}
		else
		{
			while(temp!=null)
			{
				if(temp.key.equals(key))
				{
					temp.value = value;
					size++;
					judge = true;
					break;
				}
				else
				{
					temporary = temp;
					temp = temp.next;
				}
			}
			if(!judge)
			{
				temporary.next = newnode;
				size++;
			}
		}
	}
	
	//返回此映射中的键-值映射关系数。
	public int size() {
		return size;
	}
	
	//如果此映射不包含键-值映射关系,则返回 true。
	public boolean isEmpty() {
		return size == 0;
	}
	
	//如果此映射包含对于指定键的映射关系,则返回 true。 
	public boolean containsKey(k key) {
		int hash = myhash(key.hashCode(),table.length); //由key值获得哈希值
	       Node newnode = table[hash];
	       boolean judge = false;
	       while(newnode!=null) {
	    	   if(newnode.key.equals(key)) {
	    		 judge = true;
	    		   break;
	    	   } 
	    	   else {
	    		   newnode = newnode.next;
	    	   }
	       }
	       return judge;
	}
	
	//从此映射中移除指定键的映射关系(如果存在)。 
	public void remove(k key) {
		int hash = myhash(key.hashCode(),table.length); //由key值获得哈希值
	       Node temp = table[hash];//定位当前结点
	       Node front = table[hash];//永远指向temp前面一个结点  
	       int n = 0;
	       while(temp!=null) {
	    	   n++;
	    	   if(temp.key.equals(key)) {
	    		   //找到此结点,删除
	    		   if(n==1)//首节点
	    		   {
	    			   table[hash] = temp.next;
	    			   size--;
	    			   break;
	    		   }
	    		   else if(temp.next == null)//尾结点
	    		   {
	    			   front.next = null;
	    		   }
	    		   else//位于中间的结点
	    		   {
	    			   front.next = temp.next;
	    			   temp.next = null;
	    		   }
	    	   } 
	    	   else {//没有找到相同的
	    		   if(n==1)
	    		   {
	    			   temp = temp.next;
	    		   }
	    		   else
	    		   {
	    			   front = temp;
	    			   temp = temp.next;
	    		   }
	    	   }
	       }  
	}
	
	//扩容操作函数
	public  void resize(){
		threshold = (int)(loadFactor * table.length); //阀值计算公式
		if(size > threshold) {
			Node oldtablenode;//相当于一个指针(用于旧数组)
			Node newtablenode;//(用于新数组)
			Node newjudge=null;//(新数组最后一位标记)
			Node[] newtable = new Node[table.length<<1];  //新数组扩大二倍
			//遍历旧的数组取出所有元素,再装到新的数组中去。(!!重新计算每个元素在数组中的位置)
			for(int i = 0; i<table.length;i++) {
				while(table[i]!=null)
				{
					oldtablenode = table[i]; 
					int hash = myhash(oldtablenode.key.hashCode(),newtable.length);//计算在新的数组中的位置
					//存放旧数组一个元素
					Node oldelement = new Node(hash,oldtablenode.key , oldtablenode.value);
					//将取出的元素放入新数组
					
					newtablenode = newtable[hash];
					if(newtablenode == null)
					{
						newtable[hash] = oldelement;
					}
					else
					{
						while(newtablenode!=null)//找到最后一个结点
						{
							newjudge = newtablenode;
							newtablenode = newtablenode.next;
						}
						newjudge.next = oldelement; //把旧数组元素插到最后一个结点上
					}
					oldtablenode = oldtablenode.next; //再移向下一个结点(旧数组)
				}
			}
		table = newtable; //table数组进行改变
		}
	}
	
	//改写toString方法
	@Override
	public String toString() {
		//显示效果:{1:a ,2:b}
		//定义一个数组对象
		StringBuilder sb = new StringBuilder("{");
		
		//先找到数组,再找对应的单链表
		for(int i = 0;i<table.length;i++)
		{
			Node temp = table[i];
			while(temp!=null)
			{
				//将指定的字符串追加到此字符序列
				sb.append(temp.key+":"+temp.value+", ");
				temp = temp.next;
			}
		}
		//指定索引处的字符设置为ch
		sb.setCharAt(sb.length()-1, '}');
		//返回表示此序列中的数据的字符串
		return sb.toString();
	}

	
}

猜你喜欢

转载自blog.csdn.net/qq_40301026/article/details/86660646