java常用集合: ArrayList/Vector; LinkedList ;HashMap; HashSet; LinkedHashMap;ConcurrentHashMap

1.ArrayList/Vector

ArrayList:
1.扩建不够扩大为原来的1.5倍(elementData = Arrays.copyOf(elementData, newCapacity);)
2.结构本身由动态数组实现不能序列化(由transient修饰),因此序列化时是对里面的每个元素遍历序列化。
Vector:
1.Vector 底层数据结构和 ArrayList 类似,也是一个动态数组存放数据。不过是在 add() 方法的时候使用 synchronized 进行同步写数据,但是开销较大,所以 Vector 是一个同步容器并不是一个并发容器。
(同步并发:https://blog.csdn.net/XM_no_homework/article/details/103888289)

2.LinkedList

LinkedList 底层是基于双向链表实现的
LinkedList 插入,删除都是移动指针效率很高。
查找需要进行遍历查询,效率较低。

3.HashMap

在这里插入图片描述
HashMap 底层是基于数组和链表实现的。其中有两个重要的参数:

  • 容量
  • 负载因子

容量的默认大小是 16,负载因子是 0.75,当 HashMap 的 size > 16*0.75 时就会发生扩容(容量和负载因子都可以自由调整)。

hash冲突
由于数组的长度有限,所以难免会出现不同的 Key 通过运算得到的 index 相同,这种情况可以利用链表来解决,HashMap 会在 table[index]处形成链表,采用头插法将数据插入到链表中。

put()和get():
get 和 put 类似,也是将传入的 Key 计算出 index ,如果该位置上是一个链表就需要遍历整个链表,通过 key.equals(k) 来找到对应的元素。

在 JDK1.8 中对 HashMap 进行了优化: 当 hash 碰撞之后写入链表的长度超过了阈值(默认为8)并且 table 的长度不小于64(否则扩容一次)时,链表将会转换为红黑树

假设 hash 冲突非常严重,一个数组后面接了很长的链表,此时重新的时间复杂度就是 O(n) 。

如果是红黑树,时间复杂度就是 O(logn) 。

大大提高了查询效率

4. HashSet

HashSet 是一个不允许存储重复元素的集合,内部由HashMap实现

HashSet成员变量

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

其中key为set中添加的值,value为 (private static final Object PRESENT = new Object();)
当有重复的值写入到 HashSet 时,value 会被覆盖,但 key 不会受到影响,这样就保证了 HashSet 中只能存放不重复的元素。

5. LinkedHashMap

LinkedHashMap 基于 HashMap, 但具有顺序,来解决有排序需求的场景。

它的底层是继承于 HashMap 实现的,由一个双向链表所构成。

LinkedHashMap 的排序方式有两种:

根据写入顺序排序。
根据访问顺序排序。
其中根据访问顺序排序时,每次 get 都会将访问的值移动到链表末尾,这样重复操作就能得到一个按照访问顺序排序的链表。

LinkedHashMap 其实就是对 HashMap 进行了拓展,使用了双向链表来保证了顺序性

6.ConcurrentHashMap

结构图:
在这里插入图片描述
ConcurrentHashMap由 Segment 数组、HashEntry 组成,和 HashMap 一样,仍然是数组加链表。

对Segment(分段)加锁(继承于Reentrantlock) 先访问segment再访问HashEntry

/**
 * Segment 数组,存放数据时首先需要定位到具体的 Segment 中。
 */
final Segment<K,V>[] segments;
transient Set<K> keySet;
transient Set<Map.Entry<K,V>> entrySet;

put()

put()加分段锁,先是通过 key 定位到 Segment,之后在对应的 Segment 中进行具体的 put
虽然 HashEntry 中的 value 是用 volatile 关键词修饰的,但是并不能保证并发的原子性,所以 put 操作时仍然需要加锁处理

put()过程:

  • 将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry。
  • 遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value。
  • 不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容。
  • 最后会解除在 1 中所获取当前 Segment 的锁

get()

get()不加锁,只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。

由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。

ConcurrentHashMap 的 get 方法是非常高效的,因为整个过程都不需要加锁

JDK1.8中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。

发布了28 篇原创文章 · 获赞 24 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/XM_no_homework/article/details/103888289