手把手教你用数据结构实现----------HashMap

444 阅读6分钟

今天我们来聊聊面试必备之-------HashMap

大家也知道面试以及开发中我们常用HashMap键值对形式存储元素,那么HashMap底层是怎么实现的吗?

一张图带你走进面试常问知识点

1、手写HashMap(注意:jdk7实现重要方法)

第一步:定义存放key,value的entry对象,生成有参构造

//定义存放key,value的entry对象
	class Entry<K,V>{
		public K k;
		public V v;
		public MyHashMap<K, V>.Entry<K, V> next;//定义存放key,value的节点
		//生成有参构造
		public Entry(K k, V v, MyHashMap<K, V>.Entry<K, V> next) {
			this.k = k;
			this.v = v;
			this.next = (MyHashMap<K, V>.Entry<K, V>) next;
		}
	}

第二步:定义entity数组存放<K,V>,并通过构造方法初始化容量大小

public class MyHashMap<K,V>{
	//定义一个Entry数组,存放entry对象
	private Entry<K,V>[] table;
	//初始化容量
	private static final Integer CAPACITY=8;
	//定义map底层数组大小
	private Integer size = 0;
	//构造方法
	public MyHashMap() {
		//创建数组对象
		this.table = new Entry[CAPACITY];
	}

第三步:实现put方法操作

(此操作是通过 % 取模运算保证HashCode值不能超过容量的大小)

jdk8源码是通过 &运算、^运算保证HashCode值不能超过容量的大小

public V put(K key, V value) {
		//获取键的hashcode值
			Integer hashcode = key.hashCode();
		/*通过hashcode值进行取模运算,得到数组的index值
		(模拟hashcode值存放,因为hashcode值过大,导致数组越界,所以取模运算)
		*/
			Integer index = hashcode%table.length;
			//遍历链表(查找键相同的值进行覆盖操作,hashmap允许键值相同的原理)
			for(Entry<K,V> entry = table[index];entry!=null;entry = entry.next) {
				//判断键是否相同,相同则覆盖值
				if(key.equals(entry.k)) {
					//获得之前的value
					V oldValue = entry.v;
					//新value进行赋值
					entry.v = value;
					return oldValue;
				}
			}
			addEntry(key, value, index);
		return null;
	}

源码通过位运算、异或运算来实现Hash操作

第四步:实现add添加操作,方便put方法调用

        //添加操作
	public void addEntry(K key,V value,Integer index) {
		//将key,value 存放在封装出来的entry对象中,
		table[index] = new Entry(key,value,table[index]);
		size++;//每存放一个,长度加1
	}

第五步:实现get方法操作

//获取元素操作
public V get(K key) {
		//获取键的hashcode值
		Integer hashcode = key.hashCode();
	/*通过hashcode值进行取模运算,得到数组的index值
	(模拟hashcode值存放,因为hashcode值过大,导致数组越界,所以取模运算)
	*/
		Integer index = hashcode%table.length;
		//遍历链表(查找键相同的值进行覆盖操作,hashmap允许键值相同的原理)
		for(Entry<K,V> entry = table[index];entry!=null;entry = entry.next) {
			//判断键是否相同,相同则覆盖值
			if(key.equals(entry.k)) {
				return entry.v;
			}
		}
		return null;
	}

第六步:实现size方法操作

public int size() {
		// TODO Auto-generated method stub
		return size;
	}

其他方法由读者来简单实现吧,原理都很类似的哦。

2、HashMap中能PUT两个相同的Key吗?为什么能或为什么不能?

代码体现原理

通过遍历链表,查找键相同,对值进行覆盖操作,此操作为hashmap内部允许键值相同的原理

//遍历链表(查找键相同,对值进行覆盖操作,hashmap允许键值相同的原理)
			for(Entry<K,V> entry = table[index];entry!=null;entry = entry.next) {
				//判断键是否相同,相同则覆盖值
				if(key.equals(entry.k)) {
					//获得之前的value
					V oldValue = entry.v;
					//新value进行赋值
					entry.v = value;
					return oldValue;
				}
			}

3、你知道HashMap的数据结构实现原理?

推荐详解博客地址:blog.csdn.net/hefenglian/…

HashMap通常会用一个指针数组(假设为table[])来做分散所有的key,当一个key被加入时,会通过Hash算 法通过key算出这个数组的下标i,然后就把这个<key, value>插到table[i]中,如果有两个不同的key被算了。 但有时候两个key算出的下标会是一个i,那么就叫冲突,又叫碰撞,这样会在table[i]上形成一个链表。所以 如果链表过多或过长,查找算法则会变成低性能的链表遍历,这是Hash表的缺陷。

我们都知道HashMap初始容量大小为16,一般来说,Hash表这个容器当有数据要插入时,都会检查容量有没有超过设定的thredhold,如果超过,需要增大Hash表的尺寸,但是这样一来,整个Hash表里的元素都需要被重算一遍。这叫rehash,这个成本相当的大。具体大家可以看看JDK源码

4、HashMap中的"死锁"是怎么回事?

HashMap是非线程安全,死锁一般都是产生于并发情况下。我们假设有二个进程T1、T2,HashMap容量为2,T1线程放入key A、B、C、D、E。在T1线程中A、B、C Hash值相同,于是形成一个链接,假设为A->C->B,而D、E Hash值不同,于是容量不足,需要新建一个更大尺寸的hash表,然后把数据从老的Hash表中 迁移到新的Hash表中(refresh)。这时T2进程闯进来了,T1暂时挂起,T2进程也准备放入新的key,这时也 发现容量不足,也refresh一把。refresh之后原来的链表结构假设为C->A,之后T1进程继续执行,链接结构 为A->C,这时就形成A.next=B,B.next=A的环形链表。一旦取值进入这个环形链表就会陷入死循环。

请看源码:

Entry<K,V> next = e.next;//保留头指针的下一个节点——因为是单链表,如果要转移头指针,一定要保存下一个结点,不然转移后链表就丢了
e.next = newTable[i];//插入到链表的头部——e 要插入到链表的头部,所以要先用 e.next 指向新的 Hash 表第一个元素(为什么不加到新链表最后?因为复杂度是 O(N))

newTable[i] = e;//——现在新 Hash 表的头指针仍然指向 e 没转移前的第一个元素,所以需要将新 Hash 表的头指针指向 e

e = next//——转移 e 的下一个结点

替代方案:

HashTable:对全表添加同步锁,只允许一个线程读写,不支持高并发,多线程效率低

ConcurrentHashMap内部由16个sigement快组成,对每个块加了分段锁,支持高并发,多线程效率高

使用ConcurrentHashMap进行替代,ConcurrentHashMap是一个线程安全的Hash Table。可能有人会使用HashTable。当然HashTable也是线程安全,但HashTable锁定的是整个Hash表,效率相对比较低。而ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,.

5、HashMap中的键值可以为空吗?能简单说下原理吗?

可以

原理待续。。。

6、HashMap扩容机制是怎样的,JDK7与JDK8有什么不同?

详解扩容机制博客地址:blog.csdn.net/u010890358/…


HashMap面试总结博客地址:blog.csdn.net/QXJQQQ/arti…

推荐自己的github博客地址:github.com/Lmobject


下一篇博客:手写基于双向链表实现--------LinkList