手写HashMap JDK1.7(无红黑树)
public interface MyMap <K,V>{ V get(K k); V put(K k, V v); int size(); V remove(K k); boolean isEmpty(); }
package main.java.com.hashmap; public class MyHashMap <K, V> implements MyMap<K, V>{ final static int DEFAULT_CAPACITY = 16; final static float DEFAULT_LOAD_FACTORY = 0.75f; private int capacity; private float loadFactor; private int size = 0; Entry<K,V>[] table; public MyHashMap() { this(DEFAULT_CAPACITY,DEFAULT_LOAD_FACTORY); } public MyHashMap(int capacity, float loadFactor){ this.capacity = upperMinPowerOf2(capacity); //获取为2的幂次方的容量大小 this.loadFactor = loadFactor; //加载因子,用于扩容,本次实现中尚未用到该字段 this.table = new Entry[capacity]; } private int upperMinPowerOf2(int capacity) { int power = 1; while (power <= capacity){ power *= 2; } return power; } @Override public V get(K k) { int index = k.hashCode() % table.length; Entry<K, V> current = table[index]; //遍历链表直至找到相同的hashcode while(current != null){ if(current.k == k){ return current.v; } current = current.next; } return null; } @Override public V put(K k, V v) { // 通过k换算hashcode取模绑定数组位置 int index = k.hashCode() % table.length; Entry<K,V> current = table[index]; //如果index下没有绑定元素,则直接头插法 if(current == null){ table[index] = new Entry<K,V>(k,v,null); size ++; return null; } //如果index下有元素 if(current != null){ while(current != null){ if(current.k == k){ V oldValue = current.v; current.v = v; return oldValue; } current = current.next; table[index] = new Entry<K,V>(k,v,table[index]); size ++; return null; } } return null; } @Override public int size() { return size; } @Override public V remove(K k) { int index = k.hashCode() % table.length; Entry<K, V> current = table[index]; V result = null; Entry<K,V> pre = null; while(current != null){ if(current.k == k){ result = current.v; size --; if(pre!=null){ pre.next = current.next; }else{ table[index] = current.next; } return result; } //向下遍历 pre = current; current = current.next; } return null; } @Override public boolean isEmpty() { return size == 0; } class Entry<K, V>{ K k; V v; Entry<K,V> next; public Entry(K k, V v, Entry<K, V> next) { this.k = k; this.v = v; this.next = next; } } }
package main.java.com.hashmap; public class HashMapTest { public static void main(String[] args) { MyHashMap<Integer, Integer> hashMap = new MyHashMap<>(); hashMap.put(1,101); hashMap.put(2,202); hashMap.put(3,303); hashMap.put(1,111); int[] keys = new int[]{1,2,3}; for (int i = 0; i < keys.length; i++) { System.out.println(keys[i]+"---"+hashMap.get(keys[i])); } hashMap.remove(1); hashMap.remove(3); System.out.println("hashMap size:"+hashMap.size()); System.out.println(1+": "+hashMap.get(1)); System.out.println(3+": "+hashMap.get(3)); System.out.println(2+": "+hashMap.get(2)); } }
理解:
HashMap在JDK1.8之前是数组+链表的数据结构,比如数组长度是10 (默认长度是1<<4 16), 在put存储的时候会将key换算成hashcode,并取模length的余数 0~9之间,以便于分散在数组的各个下标中;
而1%10和 11%10 的余数都是1,这种情况下会产生哈希碰撞也叫哈希冲突,1.7中才用单向链表存储这类数据,并使用头插法,查到链表的头部,而其余的元素向下移动一位;
当map中元素过多,且大于数组的75%时,数组会扩容 2的N次幂,此时,比如数组长度由10变成了20,那么取模的范围变成0~19,所有10以上的元素位置发生了变化重新计算位置也就是再哈希,其map在重新计算位置时,查询或者插入的效率会有抖动。
JDK1.8以后,hashmap是数组+链表+红黑树,对查询效率,时间复杂度进行了优化,也就是说 在 n>8 时 查询效率由n/2 优化为了 olog(n),之所以还有8位的链表,是因为n<8时,n/2 的效率是大于 olog(n)的