1. HashMap是怎样一种数据结构
数组+ 链表 + 红黑树(jdk1.8引入,后面也是按照jdk1.8的来分析)
2. HashMap为什么取值快?
HashMap在存储的时候,根据key的hash值(实际上还右移16再^hash值)&上数组的长度-1得到了在数组上的散列位置index,同样的方式,在取值的时候,和存储方式一致取得key值在数组上的散列位置index, 假如不存在散列碰撞的情况下,时间复杂度为O(1),效率很高
假如散列冲突比较多,将形成链表n个数据,则需要遍历链表,时间复杂度增大,查找的时间复杂度为O(n) (jdk1.8引入红黑树减小了因冲突查找效率低的问题,时间复杂度为log(n))。
key的hash值处理代码:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
为什么不直接用hash值去使用?
因为hash值比较大,比较耗内存。>>>即无符号右移,将得到高位的小的数字,^为异或,^的时候为右移的hash值高位都为0, ^的结果高位都会被处理掉。
为什么用&来取代%来得到在数组的散列位置?
因为&比%解释更快,hashmap中很多用到运算符代替数学运算符,如<<1 来代替乘以2,因为速度更快。
3. 为什么有些时候在知道要存储数量的时候,初始化HashMap的时候建议给容量大小初始化大小?
这就涉及到HashMap的扩容了。在没有给定HashMap,当数组大小达到阀值threthreshold = 容量大小 * 加载因子(默认0.75)的时候,就需要进行扩容了,原来的数组长度乘以2扩容出新的数组,而扩容之后做的事情也比较耗费时间,也就是需要重新再做hash值散列到新的数组时间复杂度为O(n),假如只要放100个数据,不初始化容量的话,将扩容很多次,效率来说是很低的。其实我们可以在new Hash(100),的时候就传如初始化值,将减少扩容。当然也不能初始化容量太大,也比较浪费内存。传入100,那他的容量就初始化为100了?请看初始化容量大小代码。
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;
}
假如传如cap 那得到的值将是大于cap而接近2的n次方的,假如传入3,将得到4(2的2次方),传入10,的到16(2的4次方)
HashMap的大小为什么是2的n次方
为了散列key在数组上面,采用处理过的hash&length-1(length是数组的长度),得到在数组的散列位置。
在&运算的时候,使得length-1 的二进制表示每一位都等于1,使得hash能够均匀的散列在数组上,这是一个有效减少散列碰撞的方法
4. jdk1.8之后,HashMap为为何引入红黑树?
在jdk1.7之前,key散列到数组上的发生冲突碰撞的解决方式就是使用链表来解决,用链表来解决会存在一些问题
1. 链表的长度越长,查找的时间复杂度就越大,假如长度n,时间复杂度为O(n)。
2. 多线程的情况下出现链表闭环(当然在多线程的情况下我们不建议用HashMap,可以使用ConcurrentHashMap)
在链表长度大于8的时候,链表会被转换为红黑树,红黑树是一个带颜色的自平衡二叉搜索树,时间复杂度O(log(n)),长度小于6的时候转化为链表
5. 手写HashMap
/** * 文件描述:手写实现hashmap,不考虑边界情况,只用链表去解决冲突的实现 * 作者:chenjingkun708 * 创建时间:2020/3/20 * 更改时间:2020/3/20 */ public class MyHashMap<K,V>{ private int threshold;//阀值 private float loadFactor = 0.75f;//加载因子 private Node<K,V>[] array; private String TAG = "android_test"; private int size; static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认大小16 public MyHashMap(){ } public V put(K key,V value){ //实际上可存在一个key为null if (key==null){ return null; } //1.得到hash值的处理 int h = hash(key); // Log.i(TAG,"处理过的hashcode="+h); //2. 得到数组长度-1 Node<K,V>[] tab; Node<K,V> newNode; if ((tab=array)==null){ tab = resize(); } int index = h & tab.length-1; //考虑一,散列不冲突的情况 Node<K,V> first = tab[index]; if (first==null){ newNode = new Node<>(key,value,h,null); tab[index] = newNode; }else { Node<K,V> cur = first; //查找链表有没有存在key相同的 while (cur!=null){ if (cur.hash==h&&(cur.key==key || cur.key.equals(key))){ //查找到key相同的,覆盖 cur.value = value; return value; } if (cur.next==null){ break; } cur = cur.next; } //到了这里就是没有查找到了 newNode = new Node<>(key,value,h,null); cur.next = newNode; } if (++size>threshold){ resize(); } return value; } //扩容 private Node<K,V>[] resize() { int oldCap,newCap = 0; Node[] oldArray; oldCap = array==null?0:array.length; if (oldCap>0){ newCap = array.length << 1; }else { newCap = DEFAULT_INITIAL_CAPACITY; } threshold = (int) (newCap*loadFactor); oldArray = array; array = new Node[newCap]; //要散列老的到新的上面 if (oldArray!=null&&oldArray.length>0){ for (Node node:oldArray){ if (node!=null){ while (node!=null){ int index = node.hash & newCap-1; Node next = node.next; node.next = null; if (array[index]==null){ array[index] = node; }else { Node first = array[index]; while (first!=null){ if (first.next==null){ first.next = node; break; } first = first.next; } } node = next; } } } } return array; } //处理hashcode private int hash(K key) { int h; // Log.i(TAG,"hashcode="+key.hashCode()); return key==null?0:((h = key.hashCode())^ (h >>>16)); } public int size(){ return size; } public V get(K key){ //实际上可以存在一个key为null的 if (key==null||array==null){ return null; } int h = hash(key); int index = h & array.length-1; Node<K,V> node = array[index]; while (node!=null){ if (node.hash==h&&(key==node.key||node.key.equals(key))){ return node.value; } node = node.next; } return null; } //打印所有值 public void printAll(){ if (array!=null){ for (Node<K,V> node : array){ while (node!=null){ Log.i(TAG,"key="+node.key+",value="+node.value); node = node.next; } } }else { Log.i(TAG,"数组为空"); } } //数据节点 static class Node<K,V>{ private K key; private V value; private int hash; private Node<K,V> next; public Node(K key, V value, int hash, Node<K, V> next) { this.key = key; this.value = value; this.hash = hash; this.next = next; } } }
测试:
MyHashMap myHashMap = new MyHashMap<>(); myHashMap.put('a',1); myHashMap.put('b',2); myHashMap.put("ab",3); myHashMap.put("abcd",4); myHashMap.put("adfg",5); myHashMap.put("a1","a1"); myHashMap.put("a1","aa"); myHashMap.put("a2","a2"); myHashMap.put("a3","a3"); myHashMap.put("a4","a4"); myHashMap.put("a5","a5"); myHashMap.put("a6","a6"); myHashMap.put("a7","a7"); myHashMap.put("a8","a8"); myHashMap.put('a','a'); myHashMap.printAll(); int num = (int) myHashMap.get("adfg"); Log.i("android_test","取得数字:"+num); Log.i("android_test","打印大小:"+myHashMap.size());
查看打印:
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a,value=a
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=ab,value=3
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=b,value=2
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=adfg,value=5
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=abcd,value=4
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a1,value=aa
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a2,value=a2
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a3,value=a3
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a4,value=a4
2020-03-20 18:10:38.397 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a5,value=a5
2020-03-20 18:10:38.398 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a6,value=a6
2020-03-20 18:10:38.398 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a7,value=a7
2020-03-20 18:10:38.398 14791-14791/example.ndk.cjk.datastructdemo I/android_test: key=a8,value=a8
2020-03-20 18:10:38.398 14791-14791/example.ndk.cjk.datastructdemo I/android_test: 取得数字:5
2020-03-20 18:10:38.398 14791-14791/example.ndk.cjk.datastructdemo I/android_test: 打印大小:13