1.哈希表的基本结构就是“数组+链表” 此外,JDK8中,当链表长度大于8时,为了保证查询速度,链表就转换为红黑树,小于等于6时候就又会变为链表。
原因:根据泊松分布,链表长度为8时候概率极低,转换成红黑树会占用更多的空间,为了保证均衡设为8(红黑树速度比链表快)

2.Entry[] table (将Entry放入到 table数组中) 就是HashMap的核心数组结构,我们也称之为“位桶数组”。

3.一个 Entry对象存储了:(1).key:键对象 (2).value:值对象 (3).next:下一个节点 (4).hash: 键对象的hash值

4.存储方法set的原理:

  • (1)首先调用key对象的hashcode()方法,获得hashcode。
  • (2)根据hashcode计算出hash值(要求在[0, 数组长度-1]区间) 数组初长度为16
  • (3) 一种简单和常用的算法是(相除取余算法):hash值 = hashcode%数组长度,这种算法可以让hash值均匀的分布在[0,数组长度-1]的区间。 早期的HashTable就是采用这种算法。但是,这种算法由于使用了“除法”,效率低下(jdk1.8之前)。
  • (4)JDK1.8后来改进了算法。首先根据key的值计算出hashcode的值,然后根据hashcode计算出hash值(异或其右移十六位),最后通过hash&(length-1)计算得到存储的位置,并且约定数组长度必须为2的整数幂,这样采用位运算既可实现和hashcode%length算出来的值是一样,也能提高效率,(流程图如下)
  • java 生成hash代码 java hash值怎么生成_链表

  • (5).从上图看出h = k.hashCode()) ^ (h >>> 16,为什么要 hashcode 异或其右移十六位的值: JDK1.8 优化了高位运算的算法,通过hashCode()的高16位异或低16位实现:(h = k.hashCode()) ^ (h >>> 16)。这么做可以在数组 table 的 length 比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。

5.HashMap数组的长度为什么是 2 的幂次方?

java 生成hash代码 java hash值怎么生成_hashmap_02


如图所示,length为2的幂次方,假设如果 length=16 ,那他的转化为二进制必定是11111……的形式,通过位运算&hash之后得到的数依旧是hash本身,那么可以再length长的数组长均匀分布,效率就高。

java 生成hash代码 java hash值怎么生成_链表_03


在 1、3、5、7、9、11、13、15 等奇数位处没有存放数据。因为hash值在与14(即 1110)进行&运算时,得到的结果最后一位永远都是0,0&一个数之后只能是0,也就是只能存放偶数位,即 0001、0011、0101、0111、1001、1011、1101、1111位置处是不可能存储数据的。

6.寻址方法get原理:

  • (1) 获得key的hashcode,通过hash()散列算法(上面的4.(4))定位到数组的位置。
  • (2) 在链表上挨个比较key对象。 调用equals()方法,将key对象和链表上所有节点的key对象进行比较,直到碰到返回true的节点对象为止。
  • (3) 返回equals()为true的节点对象的value对象。
  • (4).注意: Java中规定,两个内容相同(equals()为true)的对象必须具有相等的hashCode。hash值相同不一定hash和key相同因为区余算法。

7.扩容问题 :HashMap的位桶数组,初始大小为16。实际使用时,显然大小是可变的。如果位桶数组中的元素达到(0.75*数组 length), 就重新调整数组大小以2倍扩容
原因:0.75是负载因子,如果是0.5的话,数据存储一半就扩容太浪费空间,如果是1的话,数组满了再扩容会有很多哈希冲突,效率就变低了。

class HashNode2<K,V> {//结点的存储空间
	int hash ;
    K key;
    V values;
    HashNode2 next;

}
class SxtHashMap01<K,V>{
	HashNode2 [] table;//位桶数组
	int size ;
	
	public SxtHashMap01(){
	table = new HashNode2[16];
	}
	
	public void put(K key, V values) {
		//关于扩容数组问题没写   可根据ArrayList的扩容方法写出
		HashNode2 <K,V>newNode = new HashNode2<>();
		newNode.hash = Myhash(key.hashCode(),table.length);//hashCode()是Object类的方法获得的是一个独特的值(内存空间值)
		newNode.key = key;
		newNode.values= values;
		newNode.next=null;
		
		HashNode2 temp = table[newNode.hash];
		
		HashNode2 last =null;//记录结尾的结点
		boolean is = false; //用于记录如果 输入的key相同时候不需要加一个结点
		
		if(temp == null) {//数组 哈希值位置为空直接将新节点放入 ,没有哈希冲突
			
			table[newNode.hash]=newNode;
			size++;
			
		}else {//如果不为空就遍历newNode链表
		    while(temp!=null) {
		    	//如果key值相同,就覆盖
		    	if(temp.key.equals(key)) {
		    		temp.values =values;
		    		is = true;
		    		break;
		    	}else {
		    		//key值不相同就遍历下一个
		    		last=temp;
		    		temp = temp.next;
		    	}
		    		
		    }
		if(!is) {//没有重复的key值则加入到链表后面
		   last.next=newNode;
		   size++;
		}
	  }
	}

	public V get(K key) {
		//首先计算哈希值:确定存储这个结点   数组位置
		int hash = Myhash(key.hashCode(),table.length);
		
		V values=null;

		if(table[hash]!=null) {//如果查找到的hash存在那么实例化一个hashNode2的对象
			HashNode2 temp = table[hash];
		
		while(temp!=null) {//当结点不为空时候
		//然后遍历链表查找相同key的值
		if(temp.key.equals(key)) {//查找到之后存储跳出循环,没有相同的key会继续遍历
				values=(V)temp.values;
				break;
		}else {
			temp=temp.next;
		    }
	     }
      }
		return values ;
	}
	
	public int Myhash(int hashcode,int length) {//获得哈希值
		int a = hashcode&(length-1);//取模运算效率低只限定于hashcode为2的整数幂,但是分散性好,  另一种取余运算 hashcode&(length-1)效率高
		System.out.println("hash值为:"+a);
		return a;
	}
	@Override
	public String toString() {//重写toString 使用StringBuilder存储遍历
		StringBuilder sb = new StringBuilder("{");
		
		for(int i = 0;i<table.length;i++) {
			HashNode2 temp = table[i];
			while(temp!=null) {
				sb.append("key="+temp.key+" values="+temp.values+",");
				temp=temp.next;
			}
		}
		sb.setCharAt(sb.length()-1, '}');
		return sb.toString();
	}
	
} 

public class HashMap手动实现底层 {

	public static void main(String[] args) {
		SxtHashMap01<Integer,String>  m = new SxtHashMap01<>();
		m.put(11, "aa");
		m.put(22, "bb");
		m.put(33, "cc");

		m.put(53, "gg");
		m.put(69, "hh");
		m.put(85, "dd");
		
		System.out.println(m);

		System.out.println(m.get(53));
		System.out.println(m.get(69));
		System.out.println(m.get(85));
	}

}