HashMap相关知识
List(有序,可重复)
LinkedList ArrayList
改查选择 ArrayList;
增删在尾部的选择 ArrayList;
其他情况下,如果时间复杂度一样,推荐选择 ArrayList,因为 overhead 更小,或者说内存使用更有效率。null值问题
Vector 和 ArrayList
都继承自 java.util.AbstractList,底层也是用数组来实现的。
但是现在已经被弃用了,因为…它加了太多的 synchronized
一是刚才已经说过的线程安全问题;
二是扩容时扩多少的区别。ArrayList定义的新容量就是原容量的 1.5 倍。Vector默认情况下它是扩容两倍。
Queue
如果想实现「普通队列 - 先进先出」的语义,就使用 LinkedList 或者 ArrayDeque 来实现;
如果想实现「优先队列」的语义,就使用 PriorityQueue;
如果想实现「栈」的语义,就使用 ArrayDeque。
ArrayDeque 是一个可扩容的数组,LinkedList 是链表结构;
ArrayDeque 里不可以存 null 值,但是 LinkedList 可以;
ArrayDeque 在操作头尾端的增删操作时更高效,但是 LinkedList 只有在当要移除中间某个元素且已经找到了这个元素后的移除才是 O(1) 的;
ArrayDeque 在内存使用方面更高效。
所以,只要不是必须要存 null 值,就选择 ArrayDeque 吧!
那如果是一个很资深的面试官问你,什么情况下你要选择用 LinkedList 呢?
答:Java 6 以前。。。因为 ArrayDeque 在 Java 6 之后才有的。。
为了版本兼容的问题,实际工作中我们不得不做一些妥协。
Stack
Stack 在语义上是 后进先出(LIFO) 的线性数据结构。
虽然 Java 中有 Stack 这个类,但是呢,官方文档都说不让用了!
原因也很简单,因为 Vector 已经过被弃用了,而 Stack 是继承 Vector 的。
那么想实现 Stack 的语义,就用 ArrayDeque 吧:
Deque stack = new ArrayDeque<>();
Set (无序,不可重复)
那每个 Set 的底层实现其实就是对应的 Map:
数值放在 map 中的 key 上,value 上放了个 PRESENT,是一个静态的 Object,相当于 place holder,每个 key 都指向这个 object。
HashSet
采用 Hashmap 的 key 来储存元素,主要特点是无序的,基本操作都是 O(1) 的时间复杂度,很快。
LinkedHashSet
这个是一个 HashSet + LinkedList 的结构,特点就是既拥有了 O(1) 的时间复杂度,又能够保留插入的顺序。
TreeSet
采用红黑树结构,特点是可以有序,可以用自然排序或者自定义比较器来排序;缺点就是查询速度没有 HashSet 快。
HashMap
它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。
Hashtable
Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类,并且是线程安全的,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。另外键值不能为空,否则会抛出异常。(11 2n+1 16 2n)Hashtable不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。
LinkedHashMap
LinkedHashMap是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。
TreeMap
TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap。在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常
红黑树知识
首先是一种特殊的二叉搜索/查找树,其性质如下:
- 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 任意节点的左、右子树也分别为二叉查找树。
- 没有键值相等的节点(no duplicate nodes)
二叉查找树在某些情况下退化成线性链表,但红黑树一直保持o(logn)
红黑树的5个性质:
- 每个结点要么是红的要么是黑的。
- 根结点是黑的。
- 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。
- 如果一个结点是红的,那么它的两个儿子都是黑的。
- 对于任意结点而言,其到叶结点树尾端NIL指针(这里叶节点指的是空指针那些节点)的每条路径都包含相同数目的黑结点。
红黑树与avl树区别(为什么用红黑树,不用bst,avl,保持o(logn)
AVL树
1)和红黑树相比,AVL树是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1)。不管是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转是非常耗时的,由此我们可以知道AVL树适合用于插入与删除次数比较少,但查找多的情况。
2)AVL树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。
3)由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树。当然,如果应用场景中对插入、删除不频繁,只是对查找要求较高,那么AVL还是较优于红黑树。
4)相较红黑树AVL树查找性能更快
红黑树
1)红黑树放弃了追求完全平衡,而是追求大致平衡,在与AVL树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,维持平衡的时间消耗较少,实现起来也更为简单。
2)相对于要求严格平衡的AVL树来说,它的旋转次数少,对于插入、删除操作较多的情况下,选择红黑树。
3)红黑树的查询性能略微逊色于AVL树,因为其比AVL树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的AVL树最多多一次比较,但是,红黑树在插入和删除上优于AVL树,AVL树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于AVL树为了维持平衡的开销要小得多。