HashMap实现原理简析及实现的demo(一看就明白)
HashMap底层就是一个数组结构,数组中的每一项又是一个链表。
jdk源码:
1 transient Node<K,V>[] table; 2 static class Node<K,V> implements Map.Entry<K,V> { 3 final int hash; 4 final K key; 5 V value; 6 Node<K,V> next; 7 8 Node(int hash, K key, V value, Node<K,V> next) { 9 this.hash = hash; 10 this.key = key; 11 this.value = value; 12 this.next = next; 13 } 14 //..... 15 }
table就是一个Node类的数组,而Node类继承了Map.Entry<k,v>。每个 Map.Entry 其实就是一个键值对对,它还持有一个指向下一个元素的引用"next",这就构成了链表。如下图:
table数组的索引在逻辑上叫做“桶”(bucket),它存储了链表的第一个元素。而key的hashcode()方法用来找到Entry对象所在的桶,也就是寻找index。当两个key有相同的hash值,他们会被放在table数组的同一个桶里面。
好了,直接看HashMap的工作原理:
HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。
当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。
实现demo:
1 public class HashMapDemo<K, V> { 2 3 private class Entry<K, V> { 4 int hash; 5 K key; 6 V value; 7 Entry<K, V> next; 8 9 Entry(int hash, K key, V value, Entry<K, V> next) { 10 this.hash = hash; 11 this.key = key; 12 this.value = value; 13 this.next = next; 14 } 15 } 16 17 private static final int DEFAULT_CAPACITY = 1 << 4; 18 19 private Entry<K, V>[] table; 20 21 private int capacity; 22 23 private int size; 24 25 public HashMapDemo() { 26 this(DEFAULT_CAPACITY); 27 } 28 29 public HashMapDemo(int capacity) { 30 if (capacity < 0) { 31 throw new IllegalArgumentException(); 32 } else { 33 table = new Entry[capacity]; 34 size = 0; 35 this.capacity = capacity; 36 } 37 } 38 39 public int size() { 40 return size; 41 } 42 43 public boolean isEmpty() { 44 return size == 0 ? true : false; 45 } 46 47 private int hash(K key) { 48 double tmp = key.hashCode() * (Math.pow(5, 0.5) - 1) / 2; 49 double digit = tmp - Math.floor(tmp); 50 return (int) Math.floor(digit * capacity); 51 } 52 53 public void put(K key, V value) { 54 if (key == null) { 55 throw new IllegalArgumentException(); 56 } 57 int hash = hash(key); 58 Entry<K, V> nEntry = new Entry<K, V>(hash, key, value, null); 59 Entry<K, V> entry = table[hash]; 60 while (entry != null) { 61 if (entry.key.equals(key)) { 62 entry.value = value; 63 return; 64 } 65 entry = entry.next; 66 } 67 nEntry.next = table[hash]; 68 table[hash] = nEntry; 69 size++; 70 } 71 72 public V get(K key) { 73 if (key == null) { 74 throw new IllegalArgumentException(); 75 } 76 int hash = hash(key); 77 Entry<K, V> entry = table[hash]; 78 while (entry != null) { 79 if (entry.key.equals(key)) { 80 return entry.value; 81 } 82 entry = entry.next; 83 } 84 return null; 85 } 86 87 public static void main(String[] args) { 88 HashMapDemo<String, String> map = new HashMapDemo<String, String>(); 89 map.put("1", "11"); 90 map.put("1", "22"); 91 map.put("3", "33"); 92 System.out.println(map.get("1")); 93 System.out.println(map.get("3")); 94 } 95 96 }
面试小问:“如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?”除非你真正知道HashMap的工作原理,否则你将回答不出这道题。默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。
-------------------待完善