【算法】HashMap相关要点记录
? ? ? ? 在刷leetcode的算法题时,HashMap需要大量使用,而且也是面试的高频问题。这里记录了HashMap一些增、删、改、查的实现细节和时间复杂度,罗列了一些比较有用的方法,以及其它的一些细节。 ? 1、底层数据结构 1 transient Node<K,V>[] table; 2 static class Node<K,V> implements Map.Entry<K,1)"> { 3 final int hash; 4 final K key; 5 V value; 6 Node<K,1)"> next; 7 } ? ? ? 这个数组存储的Node,就包含了我们put时的K与V,K的hash值,以及指向下一个节点的指针next。数组中查询节点的时间复杂度是O(1),但是插入、删除的时间复杂度是O(n),所以执行插入和删除操作比较耗时。HashMap中加入链表结构来解决这个问题。我们知道,解决hash冲突的一般方法有:开发地址法、二次hash法、拉链法等,这里采用的就是拉链法,也就是这里的数组+链表结构了。查找元素时,最好的情况是就在数组中,时间复杂度为O(1),最坏的情况是在链表的末尾,时间复杂度是O(n)(当然,由于HashMap的扩容机制和良好的hash算法,hash冲突发生得比较少);插入和删除的时间复杂度就变成了O(1)了。 ? ? ? ? jdk1.8加入了红黑树,当链表的长度达到8的时候就会由链表升维为红黑树,当红黑树减少到6时又由红黑树降到链表。这里需要补充一点的是,红黑树的节点占用的空间比链表要大,维护红黑树的空间成本比较大,但操作方便;而链表正好相反,所以这里的8和6是一个平衡的值。在链表转为红黑树时,还会判断当前的Entry的数量是否小于64,小于64时会扩容,减少hash冲突,生成红黑树的可能性就小了很多。可见,只有当数量比较多时,维护红黑树的效率才比较明显。 ? ? ? ?红黑树的节点如下,实际上也Node的子类: class TreeNode<K,1)">extends LinkedHashMap.LinkedHashMapEntry<K,1)">2 TreeNode<K,V> parent; // red-black tree links 3 TreeNode<K,1)"> left; 4 TreeNode<K,1)"> right; 5 TreeNode<K,V> prev; needed to unlink next upon deletion 6 boolean red; 7 } ? 2、构造函数的选择 1 public HashMap() { 2 this.loadFactor = DEFAULT_LOAD_FACTOR; all other fields defaulted 3 } 4 public HashMap( initialCapacity) { 5 this(initialCapacity,DEFAULT_LOAD_FACTOR); 6 7 public HashMap(Map<? extends K,? extends V> m) { 8 this.loadFactor = DEFAULT_LOAD_FACTOR; 9 putMapEntries(m,false); 10 } 这三个构造函数都使用了默认的扩容因子, float DEFAULT_LOAD_FACTOR = 0.75f;
其值为0.75,当HashMap当前使用率达到整个容量(capacity)的75%时就会扩容。第一个构造函数使用得最频繁,会分配默认大小的容量: int DEFAULT_INITIAL_CAPACITY = 1 << 4;
? ? ? ?第二个构造函数会指定初始容量,指定容量后通过计算,会分配比该初始值大的最近的2的n次方大小的容量,比如传入的initialCapacity为12,实际上会分配16的容量,最大能分配的容量为; int MAXIMUM_CAPACITY = 1 << 30;
? ? ? ?第三个可以用于复制指定的HashMap。由于扩容需要执行不少操作,所以肯定是会占用一些资源的,如果平时开发比较明确需要使用多少容量,最好使用第二个,可以避免频繁扩容影响性能。 3、元素的插入 ? ? ? 插入元素的方法是put(K,V),其基本步骤是: ? (1)根据Key算出hash值,(n-1)&hash来确定其在数组中的index(这里的n表示数组的长度) ? (2)如果数组的这个index位置为空,则直接插入,时间复杂度是O(1),如果达到扩容条件还会扩容。 ? (3)如果数组的这个index已经有值了,那就依次遍历,比价Key来判断是否已经存在,存在就修改该节点的Value,不存在就新建节点并插在链尾。 ? (4)在第3步中,可能插入前已经是红黑树了,那就在红黑树中先查找是否存在,存在则修改,不存在则新建并插入。这样,时间复杂度是O(l)+O(logK)。所以综合来看,可以理解为插入一个元素时时间复杂度最好是O(1),最坏是O(logn) ? 6、Key为null时的处理 V put(K key,V value) { 2 return putVal(hash(key),key,value,1)">false,1)">true3 4 hash(Object key) { 5 h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 167 } ? 7、做算法题时常用的方法 1 Map<Object,Object> map = new HashMap<>(); 2 map.put(K,V); 存取KV对 3 map.get(K); 如果不存在,则返回null 4 map.getOrDefault(K,defaultValue); 相比get方法,会得到设定的默认值defaultValue。该方法很有用 5 map.entrySet(); 获取所有KV对的实体Set,其元素类型为Map.Entry<K,V>。HashMap中的Node,TreeNode都是其子类。 6 map.keySet(); 获取Key的集合Set 7 map.values(); 获取value的集合Collection,区别于Set 8 map.containsKey(K); 判断是否包含指定Key的Entry 9 map.containsValue(V); 判断是否包含指定Value的Entry 10 map.remove(K); 删除指定Key的Entry 11 map.putAll(otherMap); 复制给定的map 12 map.size(); Entry的数量 13 map.clear(); 清除所有Entry 14 map.isEmpty(); 判断是否为空 相关阅读 https://tech.meituan.com/2016/06/24/java-hashmap.html (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- Hardcoded string "下一步", should use @string r
- 使用@using (Ajax.BeginForm){}提交,不能跳转页面
- ruby-on-rails – 在ruby中使用class_eval时,如何访问原始类
- 3ds Max 和 Away3D工作流程
- c – 将float向量重新解释为unsigned char数组并返回
- as3.0视频的快进有拖动条
- Vue项目中跨域问题解决方案
- 如何在Flex ActionScript中使用[RemoteClass]可以将其用于自
- 将Jackson对象转换为JSONObject java
- reactjs – Jest抛出TypeError:无法读取未定义的属性’fet