Java STL容器

在Java中,STL(Standard Template Library)是一组容器类的集合,用于存储和操作数据。这些容器类提供了一种方便的方式来处理和管理数据,而不必关心底层的实现细节。

List、Set、Queue、Map的区别

  • List链表:存储的元素是有序的、可重复的
  • Set集合:存储的元素是不可重复的
  • Queue队列:按照特定的排队规则来确定先后顺序,有序、可重复
  • Map队列:使用键值对存储,每个键最多映射到一个值

ArrayList和LinkedList的区别

ArrayList LinkedList
线程安全? 不同步,不保证安全 不同步,不保证安全
底层数据结构 数组 双向链表
插入和删除是否受位置元素影响 影响 不影响
是否支持快速随机访问 支持 不支持
内存空间占用 空间浪费体现在链表尾部会预留一定的容量空间 每个元素都要比arrayList消耗更多的空间【存储后继、直接前驱及数据】

HashMap的底层原理

主要用来存放键值对,基于哈希表的map接口实现,是非线程安全的。

HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个

JDK1.8之前

由数组+链表组成,数组是HashMap的主体,链表主要是为了解决哈希冲突而存在的(拉链法)

拉链法:

就是将数组和链表结合,也就是创建一个链表数组,数组中的每个格就是一个链表。如遇到哈希冲突,就把冲突值加到链表中即可

JDK1.8

由数组+链表+红黑树,解决哈希冲突的方式有改变

  • 当链表长度大于8(链表转为红黑树之前会进行判断,如果数组长度小于64,会优先对数组进行扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间

这里为什么用红黑树,而不是其他树?

红黑树查询时间复杂度O(logn),适用于大量的插入和删除。

当节点数目比较少时,B树/B+树会挤在一起成链表,而链表查询效率比较低。

HashMap的长度为什么是2的幂次

HashMap的初始化大小是16,每次扩容,容量会变为之前的2倍。因此HashMap 总是使用 2 的幂作为哈希表的大小。

HashMap为什么是线程不安全的

JDK1.8之前的版本

多线程环境下扩容可能存在死循环和数据丢失的问题。【当一个桶位中有多个元素需要扩容时,多个线程同时对链表进行操作,头插法可能导致链表中的节点指向错误的位置,从而形成一个环形链表,进而使得查询元素的操作陷入死循环而无法结束】

JDK1.8 版本

HashMap 采用了尾插法而不是头插法来避免链表倒置,使得插入的节点永远都是放在链表的末尾,避免了链表中的环形结构。

在 HashMap 中,多个键值对可能会被分配到同一个桶(bucket),并以链表或红黑树的形式存储。多个线程对 HashMap 的 put 操作会导致线程不安全,具体来说会有数据覆盖的风险

举个栗子

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    // ...
    // 判断是否出现 hash 碰撞
    // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    // 桶中已经存在元素(处理hash冲突)
    else {
    // ...
}
  • 两个线程 1,2 同时进行 put 操作,并且发生了哈希冲突(hash 函数计算出的插入下标是相同的)。
  • 不同的线程可能在不同的时间片获得 CPU 执行的机会,当前线程 1 执行完哈希冲突判断后,由于时间片耗尽挂起。线程 2 先完成了插入操作。
  • 随后,线程 1 获得时间片,由于之前已经进行过 hash 碰撞的判断,所有此时会直接进行插入,这就导致线程 2 插入的数据被线程 1 覆盖了。

HashMap和Hashtable的区别

是否线程安全 null 初始容量和扩容大小
HashMap 支持一个null key,多个null value 初始容量16,扩容大小2n
Hashtable 是【内部方法由synchronized 修饰
不允许 默认容量11,扩容2n+1

ConcurrentHashMap实现方法不同jdk1.7和jdk1.8

  • 线程安全实现方式:JDK 1.7 采用 Segment 分段锁来保证安全, Segment 是继承自 ReentrantLock。JDK1.8 放弃了 Segment 分段锁的设计,采用 Node + CAS + synchronized 保证线程安全,锁粒度更细,synchronized 只锁定当前链表或红黑二叉树的首节点。
  • Hash 碰撞解决方法 : JDK 1.7 采用拉链法,JDK1.8 采用拉链法结合红黑树(链表长度超过一定阈值时,将链表转换为红黑树)。
  • 并发度:JDK 1.7 最大并发度是 Segment 的个数,默认是 16。JDK 1.8 最大并发度是 Node 数组的大小,并发度更大。

你可能感兴趣的:(集合,开发语言,java)