集合分为Collection / Map
-
Collection对象之间没有指定的顺序,允许有重复元素和多个null元素对象;
它是Set和List接口的父类,是一种最通用型的集合接口。
集合的分类
- List:有序列表(可以有重复元素)集合
- Set:没有重复元素的集合
- Map:通过键值对查找映射表的集合——需要排序用 TreeMap
集合细分
- List——有序存储
- LinkedList:双向链表结构,适合插入删除(线程不安全)
- LinkedList 查找慢的原因是找每个元素要从头开始遍历
- ArrayList:动态数组结构,适合随机访问(线程不安全)
- Vector:数组结构,Stack 是 Vector 的实现类(线程安全)
- LinkedList:双向链表结构,适合插入删除(线程不安全)
- Set——不可重复
- 不可重复的实现:往HashSet中存放元素时,默认会对数据和集合中的数据进行比较,首先会比较它们的Hashcode
- 如果 hashcode 不同,则直接插入
- 当Hashcode相同时
- 再进行equals()比较,如果返回true则证明两个元素相同,将不对该元素进行存放
- 如果是false,则会进行散列,然后存放。
- hashcode 的产生
- 每个对象肯定有一个物理地址,将物理地址对应的整数通过 hash 函数,就得到一个 hashcode,也就是 hash 表中的位置(hashcode 值相同,说明在同一个散列存储结构中)
- HashSet 使用 hash 表(数组)存储元素
- 不需要自动排序就用LinkedHashSet
- TreeSet 底层是 TreeMap,在 TreeSet 中存入数据,相当于存入 TreeMap 的 key 值,在 TreeSet 排序存储后,就有value 值了。
- 不可重复的实现:往HashSet中存放元素时,默认会对数据和集合中的数据进行比较,首先会比较它们的Hashcode
- Map——key 值不可重复
- HashMap 线程不安全,效率高,允许 key 和 value 为 null
- HashTable 是线程安全的,这个类中一些方法加入了 synchronized 关键字
- TreeMap ——非线程安全,基于红黑树对所有 key 排序
HashMap
HashMap 的数据结构
- HashMap 在 JDK 1.8 以前的版本中,HashMap 的实现是数组+链表,当有大量元素时,链表会很长,遍历起来很麻烦
- JDK 1.8 以后,HashMap的实现依靠 数组+红黑树 或 数组+链表。添加元素时,若桶中链表元素超过8个,链表就会转换成红黑树;删除元素和扩容时,若桶中结构为红黑树并且树中元素较少时,进行剪枝或还原成链表(少于6个)。遍历查找的时间复杂度为 O(logn),性能较高。
HashMap 的工作原理
- HashMap 数组中的每个元素都是链表,由Node内部类(实现
Map.Entry<K,V>
接口)实现,HashMap
通过put&get
方法存储和获取元素 - 存储元素
put()
方法- 调用hash(K)方法计算K的hash值,然后结合数组长度,计算得数组下标
- 调整数组大小(当容器中的元素个数大于capacity*loadfactor时,容器会进行扩容resize为2n)
- 哈希碰撞
- 如果K的
hash
值在HashMap
中不存在,则执行插入,若存在,则发生碰撞; - 如果K的
hash
值在HashMap
中存在,且它们两者equals
返回true
,则更新键值对; - 如果K的
hash
值在HashMap
中存在,且它们两者equals
返回false
,则插入链表的尾部(尾插法)或者红黑树中。
- 如果K的
- 获取元素
get()
方法- 调用hash(K)方法(计算K的hash值)从而获取该键值所在链表的数组下标
- 顺序遍历链表,equals()方法查找相同Node链表中K值对应的V值
HashMap 的线程不安全
- 在jdk1.7中,在多线程环境下,扩容时会造成环形链或数据丢失。
- 在jdk1.8中,在多线程环境下,会发生数据覆盖的情况。
ConcurrentHashMap
ConcurrentHashMap 与 HashMap 的区别
- ConcurrentHashMap 对整个桶数组进行了分割分段,然后在每一个分段上都用lock锁进行保护,线程安全(HashMap 线程不安全)
- HashMap 键值对允许有 null,ConcurrentHashMap 都不允许。
ConcurrentHashMap 与 HashTable 的区别
- 底层数据结构:JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用数组+链表/红黑二叉树,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
- 实现线程安全的方式:
- ConcurrentHashMap 使用分段锁,在 JDK1.7 时,ConcurrentHashMap 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 JDK1.8 时,用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作
- HashTable 使用 synchronized (对对象加锁)来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
- ConccurentHashMap 的默认并发度是16,根据用户设置16,32…
红黑树
- 特点:
- 每个节点非红即黑
- 根节点总是黑色的
- 如果节点是红色的,则它的子节点必须是黑色的(反之不一定)
- 每个叶子节点都是黑色的空节点(NIL节点)
- 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)