(java集合框架篇) 本文从线程,数据结构,基本用法三个方面对常用集合展开进行阐述

要在程序中处理数据,离不开集合的帮忙。要想安全操作集合,就需要了解集合的数据结构,以及如何保证并发操作数据和程序的安全性。本文通过常见集合的整理,方便我们了解各个集合的特点。

1.java集合框架一览

(ps:这里网上找了一份大佬的图,如有侵权,联系我删除,先感谢一下大哥🙏)
在这里插入图片描述

2. 如上可以看到java体系中包含两大块:List和Map

2.1 List接口

2.1.1 常用子类及实现

2.1.1.1 ArrayList
  • 线程安全:非线程安全。
  • 数据结构:动态数组。
  • 线程安全实现:
    可以使用 Collections.synchronizedList() 方法包装为线程安全的 List。
    使用 CopyOnWriteArrayList 替代(本质是一个线程安全的 ArrayList 实现)。
  • 扩容机制:
    默认初始容量为 10。
    每次扩容时,新容量为旧容量的 1.5 倍(即 newCapacity = oldCapacity + (oldCapacity >> 1)(ps: >> 1表示大小为原来一半))。
  • 操作影响:
    add:在尾部插入元素,若容量不足触发扩容,导致数组复制。
    remove:删除后,后续元素前移。
  • 遍历和递增数据:
    普通遍历时,插入或删除元素会导致 ConcurrentModificationException。(实际上称之为:fail-fast。)
  • 不能删除元素原因:
    Iterator 是集合提供的专用迭代工具,内部维护了一个修改计数器 (modCount) 和一个迭代器自己的期望计数器 (expectedModCount)。
    当集合结构发生修改(例如添加或删除元素)时,modCount 会增加。
    如果通过 Iterator 的 remove() 方法删除元素,expectedModCount 会与 modCount 同步更新,迭代器认为这是合法修改,因此不会抛出异常。而普通for循环操作增删元素只会修改modCount变量,一次系统认为modCount和expectedModCount不想等,代表并发修改,虽然实际上不是并发操作。但是此时会抛出一场给用户。
2.1.1.2 LinkedList
  • 线程安全:非线程安全。
  • 数据结构:双向链表。
  • 线程安全实现:
    使用 Collections.synchronizedList() 方法包装为线程安全的 List。
  • 扩容机制:链表无需扩容,动态调整。
  • 操作影响:
    add:插入元素时,创建新节点,调整前后节点引用。
    remove:删除元素时,调整前后节点引用。
  • 遍历和递增数据:
    遍历时插入/删除会导致链表结构变化,应使用 Iterator。
2.1.1.3 Vector
  • 线程安全:线程安全(内部暴露出来的方法都是synchronized关键字锁住的,以此来保证线程操作的安全性)。
  • 数据结构:动态数组。
  • 扩容机制:
    默认初始容量为 10。
  • 每次扩容时,若未指定增量容量,则扩容为旧容量的两倍。
  • 操作影响:
    add 和 remove 类似于 ArrayList,但线程安全开销较大。
  • 遍历和递增数据:
    遍历时线程安全,但可能出现数据不一致的逻辑问题。
2.1.1.4 CopyOnWriteArrayList
  • 线程安全:线程安全。
  • 如何保证线程安全(概述):
    1.在做修改操作的时候加锁
    2.每次修改都是将元素copy到一个新的数组中,并且将数组赋值到成员变量array中。
    3.利用volatile关键字修饰成员变量array,这样就可以保证array的引用的可见性,每次修改之前都能够拿到最新的array引用。这点很关键。
  • 数据结构:基于数组,每次写操作会复制整个数组。
  • 线程安全实现:通过写操作时复制整个数组来保证线程安全。
  • 扩容机制:同 ArrayList。
  • 操作影响:
    add:复制数组后插入元素,性能较低。
    remove:复制数组后删除元素。
  • 遍历和递增数据:
    迭代器基于快照,因此不会抛出 ConcurrentModificationException。

2.2 Map接口

2.2.1 常用子类及实现

2.2.1.1 HashMap
  • 线程安全:非线程安全。
  • 数据结构:数组 + 链表(Java 8 以后链表超过阈值时转换为红黑树)。
  • 线程安全实现:
    使用 Collections.synchronizedMap() 包装为线程安全。
    使用 ConcurrentHashMap 替代。
  • 扩容机制:
    默认初始容量为 16。
    当负载因子(默认 0.75)超出阈值时,扩容为原数组容量的两倍,重新分布元素。当我们new一个HashMap的时候,当我们默认制定一个大小的时候,实际上会向2的幂次方靠,例如我们指定了大小为11,实际上会开辟一个大小为2^4=16的map。因为后续扩容需要基于2的幂次方扩容,即2倍。
  • 操作影响:
    put:插入元素时,可能触发扩容。
    remove:删除元素时调整链表或树。
  • 遍历和递增数据:
    遍历时插入/删除会抛出 ConcurrentModificationException,应使用 Iterator。
2.2.1.2 LinkedHashMap
  • 线程安全:非线程安全。
  • 数据结构:基于 HashMap,并维护一个双向链表记录插入顺序。
  • 线程安全实现:
    使用 Collections.synchronizedMap() 包装。
    扩容机制:与 HashMap 相同。
  • 操作影响:
    put 和 remove 同 HashMap,但链表顺序会变化。
  • 遍历和递增数据:
    遍历时顺序稳定,可插入/删除但需使用 Iterator。
2.2.1.3 ConcurrentHashMap
  • 线程安全:线程安全。
  • 数据结构:分段锁机制(将哈希表分为16个区域,即segment,分别对每个区域加锁。Java 8 后使用 CAS 和分区数组 + 红黑树)。
  • 线程安全实现:
    通过锁分段和 CAS 操作实现高效线程安全。
  • 扩容机制:
    默认初始容量为 16。
    扩容为原容量的两倍,按分区扩展。
  • 操作影响:
    put 和 remove:高效线程安全,但可能触发扩容。
  • 遍历和递增数据:
    遍历时可能包含插入/删除后的数据,但不会抛出异常。
2.2.1.4 TreeMap
  • 线程安全:非线程安全。
  • 数据结构:红黑树。
  • 线程安全实现:
    使用 Collections.synchronizedSortedMap() 包装。
  • 扩容机制:无需扩容,动态调整红黑树结构。
  • 操作影响:
    put 和 remove 会调整红黑树结构。
  • 遍历和递增数据:
    顺序按照键的自然顺序或比较器顺序。

3.总结

安全使用 Java 集合需要根据场景选择合适的集合类型,如单线程使用普通集合,多线程使用线程安全集合(如 CopyOnWriteArrayList 或 ConcurrentHashMap)。在遍历中删除元素时,优先使用 Iterator 的 remove() 方法,避免直接修改集合引发 ConcurrentModificationException。对于多线程场景,可通过同步块或线程安全集合避免数据竞争,尽量使用原子操作。初始化时建议预估容量以减少扩容开销,且在不需要修改时使用不可变集合。此外,应根据性能需求选择适当的遍历方式和集合实现,避免死锁及不必要的开销。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值