java集合

  1. 继承体系
    • Collection、Map、List、Set、Queue
    • 是否有序、是否可重复、键值对
  2. 集合fail-fast机制
    • 错误检测机制, 防止对非线程安全集合的并发修改
    • ConcurrentModificationException
  3. 如何创建不可变集合
    • Collections#unmodifiableXxx
    • 进行修改操作时抛出异常UnsupportedOperationException
  4. 如何迭代时删除元素
    • 迭代器遍历删除
    • fori删除会导致有些元素访问不到; foreach删除抛出异常, 因为底层使用迭代器
  5. ArrayList和LinkedList区别
    • 数组、链表
    • 连续内存、常数时间内随机访问
  6. ArrayList和Vector区别
    • 是否线程安全、扩容策略
  7. Vector的size为什么要同步, 扩容策略2和1.5区别
    • ?
    • 倍增则均摊O(1)复杂度, 固定增长O(N)
    • 倍增导致下一次申请的内存必然大于之前所有分配总和, 可能无法复用
  8. 如何创建线程安全集合
    • Collections#synchronizedXxx
  9. List实现类
    • ArrayList、LinkedList、CopyOnWriteArrayList、Vector

  1. HashMap和Hashtable区别

    • 线程安全性、null键值
    • 哈希值, 前者重新计算
    • 扩容, 前者扩容为2倍+1, 后者2倍扩容.
    • 结构/解决哈希冲突办法, 前者数组+链表, 后者数组+链表+红黑树
  2. HashMap在JDK和JDK8区别

    • 数组 + 链表 + 红黑树
    • 插入改尾插, 不会出现多线程下循环链表的问题
    • 哈希值处理, JDK7不处理
  3. 基本原理?HashMap为什么使用数组+链表?哈希冲突的解决办法

    • 使用Node数组存储k-v值, Node实际是一个单向链表
    • 开放定址法、链地址法、再哈希法、公共溢出区
    • 开放定制: 线性探测、平方探测, ThreadLocal使用线性探测
    • 链地址法: 链表存储冲突元素
    • 再哈希法: 冲突则使用另一个哈希函数再哈希
    • 公共溢出区: 冲突的全部使用List存储, 那么若哈希冲突则需要遍历这个List
  4. LinkedList代替数组可以吗

    • 可以, 但是效率很低, 因为需要本质上是需要常数时间内的随机访问
  5. ArrayList可以吗

    • 不可以, 因为ArrayList的扩容固定了, 而HashMap要求倍增
  6. HashMap什么条件下扩容?为什么扩容2次幂?hash值计算&为什么?为什么默认装载因子0.75

    • 当容量到达 数组长度*装载因子 扩容
    • 为了便于使用位运算计算元素所在桶, 高效计算
    • hash ^ (hash >>> 16), 容量较少时利用到高位, 降低膨胀几率
    • 太大空间利用率低太小碰撞几率增加. 0.6-0.8合适, 取0.75折衷结果.
      • 若Hash分布均匀, 则碰撞符合泊松分布, 达到8个节点的概率小于千万分之一
  7. put流程

    • 数组为null或长度为0先初始化, 默认容量16
    • 计算哈希值计算索引, 没有碰撞则直接存储
    • 碰撞若第一个节点k相同直接替换val
    • 不同则判断节点类型, 分别插入树和链表, k同则值换
    • 若链表节点插入新节点, 若链表长度大于等于8且同容量大于等于64, 树化, 小于64扩容
    • 最后容量大于扩容阈值则扩容
  8. get流程

    • k计算hash值计算索引
    • 桶节点为null则没有值, 返回null
    • 否则第一个节点k值相等返回值
    • 否则对链表节点/树查找
  9. 哈希算法

    • 把大范围值映射到小范围(或固定长度小范围), 又称摘要算法Digest
    • MD4、MD5, MD Message Diagest, 输出长度16B. MD5输出较短, 短时间内破解是可能的, 不推荐使用
    • SHA-1, 数组20B, SHA-256输出32B, SHA-256输出64B
    • BASE64
    • 哈希算法输出越长, 越难产生碰撞, 越安全
    • 可以加盐, 也就是加上随机随机字符再进行哈希运算, 那么就难以逆推
      • 逆推: 计算常用密码的MD5, 然后根据MD5碰撞尝试是否为这个常用密码
      • 加盐: 碰撞出字符串, 我们也会加上盐, 加盐后MD5就比对不一样了
    • 文件校验: 是否被修改、文件是否一致
  10. String的哈希计算

    • 31为权, 对每一个字符进行加权多项式计算, 自然溢出值为hashCode
    • 31为质奇数, 运算可优化为位运算
    • 简单、运算块、分布较为均匀
    • 缓存hashCode + 不可变对象
    • 【原理】
  11. JDK8改了啥?为什么不直接使用红黑树?BST可以吗?阈值为什么是8?什么时候退化

    • 加了红黑树、哈希运算优化、链表尾插
    • 红黑树需要旋转平衡, 元素较少时实际效率差别不大, 较多时才能使用查询效率抵换平衡花费
    • BST可能退化为链表
    • k随机均匀、装载因子0.75, 则一个桶中元素遵循λ为0.5的泊松分布, 8时概率低于千万分之一
    • 6, 有一个节点的缓冲, 避免频繁转换
  12. 并发问题

    • JDK7多线程扩容死循环, JDK8没有
    • put丢失无法get
    • 使用线程安全集合Map
  13. 什么键适合作为k

    • 不可变对象, String、包装类
    • 键可以为null
    • 可变k可能导致无法get出来
  14. 自定义对象作为键要如何处理

    • 最好是不可变对象, final 修饰实例属性, 构造函数初始化
    • hashCode 和 equals 进行重写
    • hashCode 缓存
    • 不提供getter
    • 传入可变类进行初始化则需要clone
  15. Map实现类

    • 有序?线程安全?按什么排序?
    • HashMap、LinkedHashMap、TreeMap、WeakHashMap
    • ConcurrentHashMap、ConcurrentSkipListMap
    • Collections.synchronizedMap、unmodifiableMap
    • 强软弱虚引用
  16. 缓存淘汰策略

    • LRU: 最近最少使用, 最长时间没有访问的. LinkedList 重写 removeEldestEntry 根据策略移出元素
    • LFU: 最不经常使用的
  17. ConcurrentHashMap

    • JDK7的锁Segment, 每个Segment多个桶, JDK8直接锁桶, 丢弃Segment的实现并加入红黑树, 同时使用synchronized而非Lock
    • JDK8没有哈希冲突时直接CAS, 否则才会锁住整个桶
    • 不支持键值的null
    • 写加锁, 读不加锁
    • Hashtable强一致性, ConcurrentHashMap 若一致性
    • 协助扩容
      • 认领一个区间进行扩容, 会反应到实例变量中, 因此每一个线程会领取到合适的区间
        • 最小16个桶
        • len / 8 / NCPU, 若NCPU为1则全部桶
        • 当处理完自己的区间, 回去看一下是否领取完毕, 没有则再次领取一个区间
      • 扩容时节点新new, 所以不影响原数组, 不影响get操作. 已处理好的则一个新的数据类型节点转发到新的数组桶
  18. ConcurrentSkipListMap

    • 跳表, 可以进行二分查找的链表
    • 在链表上面添加多级索引, 空间换时间, 最底层所有元素双向链表, 可范围查找
    • 插入元素时, 随机数找到其需要建立索引等层级, 然后插入各层位置
      • 随机数 & 0x80000001: 非负偶数才处理
      • 判断值从第二为开始有几个连续1, 就几层+1, 最底层1层, 最小也是1层., 0110110为3, 01100为1
      • 超过现有层级则最多超过1层
  19. 线程安全的Map都不允许null值null键

    • getKey得到了值并不能判断val是null还是没有此元素, 而单线程不担心因为本来就不不在多线程下使用
  20. 什么时候使用TreeMap

    • 当需要有序遍历的时候
  21. Set实现类

    • HashSet、LinkedHashSet、TreeSet
    • CopyOnWriteArraySet、ConcurrentSkipListSet
    • Collections.newSetFromMap 转 Map 为 Set, 可获得 ConcurrentHashSet
  22. 并发队列: ArrayBlockingQueue、LinkedBlockingQueue、ConcurrentLinkedQueue 区别

    • ArrayBlockQueue: 定容, 一把锁 ReentrantLock + Condition 实现
    • ConcurrentLinkedQueue: 非定容, CAS + 重试 实现, 入队速度很快
    • LinkedBlockingQueue: 定容(MAX), 两把锁实现, 出队入队互不影响

  1. Queue实现类

    • PriorityQueue 【非定容, 不允许为null】

    • 小根堆实现, 及返回最小元素
      +-----------------------------+
      | 操作 | 抛出异常 | 返回特定值|
      +-----------------+-----------+
      | 入队 | add | offer |
      | 出队 | remov | poll |
      | 队首 | element | peek |
      +------+----------+-----------+

    • ConcurrentLinkedQueue【非定容】

      • 非阻塞队列
      • 线程安全
    • ArrayDeque

    • LinkedList

    • 非线程安全的均非定容

  2. 阻塞队列

    • ArrayBlockQueue 【定容, 必须指定容量, 不允许为null】

      • 只是用了一把锁来控制入队与出队
      • 可传入使用公平锁和非公平锁, 默认非公平
        +-----------------------------+----------------+
        | 操作 | 抛出异常 | 返回特定值| 阻塞 | 超时 |
        +-----------------+-----------+-------+--------+
        | 入队 | add | offer | put | offer |
        | 出队 | remov | poll | take | poll |
        | 队首 | element | peek | | |
        +------+----------+-----------+-------+--------+
    • LinkedBlockingQueue【定容, 默认容量整型最大值, 不允许为null】

      • 两把锁分别锁入队和出队
    • Synchronous【没有被消费前阻塞, 不允许为null】

      • [ˈsɪŋkrənəs] 同步的, 同时的
      • 无缓冲阻塞队列
      • 两线程间移交元素
      • 实际会缓冲, 比如生产者多, 则内部存储起来, 且阻塞生产者, 直到被消费
    • PriorityBlockingQueue【非定容, 不允许为null】

    • LinkedTransferQueue【不允许为null】

      • LinkedBlockingQueue、SynchronousQueue(公平模式)、ConcurrentLinkedQueue三者的集合体
      • 生产者可以控制是否阻塞
      • 消费者还是会阻塞
    • DelayQueue【非定容, 不允许为null】

      • 阻塞队列
      • SchduledThreadPoolExecutor不是直接使用, 而是重新进行实现的

    -阻塞队列与非阻塞队列的区别
    - 是否提供阻塞方法: put、take

  3. 线程池

    • Executors.newSingleThreadExecutor: 1核心线程1最大线程 + LinkedBlockingQueue
      • 问题: MAX定容, 任务过多导致OOM
    • newFixedThreadPool: 核心最大一样, LinkedBlockingQueue
      • 问题: MAX定容, 任务过多导致OOM
    • newCachedThreadPool: 无核心线程, 最大MAX + SynchronousQueue + 60s超时
      • 问题: MAX线程数导致OOM
    • DelayedWorkQueue: 核心设置 + 最大MAX + DelayedWorkQueue
      • 问题: 最大线程数MAX, 但是 DelayedWorkQueue 最大MAX
      • 因此问题还行MAX队列

【List】
【没有固定容量的】
ArrayList
- 默认容量10
- 传入容量立即分配空间
- 1.5倍扩容
- 不会默认缩容
- 常数时间内的随机访问
- 适合尾部增删
LinkedList
CopyOnWriteArrayList
- 读写分离, 写时复制, 阻塞写不阻塞读
- 弱一致性, 不保证实时一致性, 只保证最终一致性
- ArrayList的线程安全版本
- 适合读多几乎不写的场景
Vector


【Map】
【无序、有序、线程安全】
+-------------------------+--------------------------------------+
| Map | 键值 |
+-------------------------+--------------------------------------+
| HashMap | 均允许为null |
| LinkedHashMap | 均允许为null | 继承HashMap
| WeakHashMap | 均允许为null |
| TreeMap | 有比较器则允许, 没有则k不能为null |
| ConcurrentHashMap | 均不允许为null | 固定装载因子
| ConcurrentSkipListMap | 均不允许为null |
+-------------------------+--------------------------------------+
HashMap
- 允许null键null值
- 数组 + 链表 + 红黑树
- bucket桶
- 容量必须为2的次幂
- 默认装载因子0.75
- 树化阈值8 + 64桶
- 链化阈值6
- Node单链表节点, newNode创建, 便于LinkedHashMap扩展维护双链表
- hash函数特殊
- 不会立即开辟空间
- put可以相同时替换key并调用 afterNodeAccess, 便于LinkedHashMap扩展维护LRU
- put结束后调用了 afterNodeInsertion(removeEldestEntry), 便于LinkedHashMap扩展维护LRU

LinkedHashMap
- 允许null键null值
- 继承 HashMap
- 重写 newNode 维护双链表
- 能按照插入顺序排序, 也能按照访问顺序排序, 可以实现LRU缓存

TreeMap
- 继承 SortedMap、Navigable, 注意 LinkedHashMap 没有
- 红黑树(红色或黑色、Root黑色、红色孩子黑色、任意节点到所有叶子节点路径上黑色节点数相同、BST)
- 有比较器则允许null键, 否则不允许

ConcurrentHashMap
- 不允许null键null值
- HashMap线程安全版本, 数组 + 链表 + 红黑树
- 相比Hashtable效率极大提高, 不过不是强一致性的(Hashtable的get和size均同步)
- synchronized + CAS + 自旋 + 分段锁
- size存储类似 LongAddr, 分段存储, 不同线程哈希到不同的段
- 读操作不加锁, 不是强一致性的

ConcurrentSkipListMap
- 键值均不允许为null
- 跳表实现, 实质是可以进行二分查找的有序链表, 原链表基础上加上多级索引(缓存), 通过索引实现快速查找
- Redis使用跳表实现有序列表是因为跳表实现较为简单易读, 且范围查询效率较高
- List存储
- 索引层数决定
- ThreadLocalRandom.nextSecondarySeed 随机数
- 正偶数
- 有多少个1就建立几层
- 超过现有最高层则最多高一层且头结点也新建一层


【Set】
+-----------------------+-------------+
| Set | 元素 |
+-----------------------+-------------+
| HashSet | 允许为null | HashMap字段, Object值, 没有get, 三个参数的构造函数使用LinkedHashMap
| LinkedHashSet | 允许为null | 继承HashSet, 这里HashSet的三参构造函数有了用处, 基本上没有自己的API, 不支持访问顺序排序
| TreeSet | 有比较器允许| 字段默认TreeMap, 也可传NavigableMap子类(ConcurrentSkipListMap)
| ConcurrentSkipListSet | 不允许 | ConcurrentSkipListMap字段
| CopyOnWriteArraySet | 允许 | CopyOnWriteArrayList字段
+-----------------------+-------------+
LinkedHashSet并没有实现SortedSet接口, LinkedHashMap也没有SortedMap


【Queue】
+-----------------------------+----------------+
| 操作 | 抛出异常 | 返回特定值| 阻塞 | 超时 |
+-----------------+-----------+-------+--------+
| 入队 | add | offer | put | offer |
| 出队 | remov | poll | take | poll |
| 队首 | element | peek | | |
+------+----------+-----------+-------+--------+

【均不允许元素为null】
+---------------------------+--------------------------+
| 队列 | 是否定容 |
+---------------------------+-------------- -----------+
| ArrayBlockingQueue | 是 |
| LinkedBlockingQueue | 定容, 默认MAX |
| SynchronousQueue | 双方均会阻塞 |
| PriorityBlockingQueue | 否 |
| LinkedTransferQueue | 消费者阻塞, 生产者可控 |
| DelayQueue | 否 |
| ScheXxx.DelayedWorkQueue | 否 |
+---------------------------+--------------------------+
+-----------------------+
| Queue | 全不允许元素为null
+-----------------------+
| PriorityQueue | 小根堆, 非定容
| ArrayBlockingQueue | 定容, 必须传入容量, 立即扩容, 出队入队一把锁
| LinkedBlockingQueue | 定容, 默认容量MAX, 出队入队分别锁, 效率较高
| SynchronousQueue | 交换元素
| PriorityBlockingQueue | 非定容, 小根堆
| LinkedTransferQueue | 相对SynchronousQueue效率更高
| ConcurrentLinkedQueue | 线程安全, 但不是阻塞队列, 非定容
| DelayQueue | PriorityQueue + 锁实现
| ArrayDeque | 循环数组实现双端队列
| LinkedList | 双链表
+-----------------------+

================================================================================

  1. 线程安全 Collections.synchronizedXxx
  2. 不可变 Collections.unmodifiableXxx
  3. Collections.newSetFromMap
  4. Collections.EMPTY_XXX
  5. 单个元素 Collections.singletonXxx
  6. N个重复元素的List Collections.nCopies

LRU、LFU
多少个1的位运算(与上数-1)
LRU & LFU缓存机制的原理及实现 - 知乎 (zhihu.com)

Redis LRU 算法和LFU算法 - 知乎 (zhihu.com)

LRU(Least Recently Used):最近最少使用淘汰,最长没有访问

LFU(Least Frequently Used):挑选最不经常使用的数据淘汰,访问次数最少

哈希算法: 随机、MD4、MD5, 大范围映射到小范围

面试 ConcurrentHashMap ,看这一篇就够了! - 知乎 (zhihu.com)

为什么ConcurrentHashMap是弱一致的 | 并发编程网 – ifeve.com

ThreadLocal
- Thread 的实例变量 ThreadLocal.ThreadLocalMap
- 单个 ThreadLocal 实例, 每个 ThreadMap<ThreadLocal实例, 值>
- ThreadLocal如何实现线程安全: 每个 Thread 都有自己的 Map, 都有一个键为 ThreadLocal 实例, 于是一个 ThreadLocal 实例在多个
Thread 中共存, 但是每个 Thread 中相同的 ThreadLocal 实例对应的值是多例的, 每个线程不一样
- Map
- 哈希冲突解决: HashMap 链地址, ThreadLocal 开放定址-线性探测
- 2倍扩容, 易于计算 index
- 阈值 2 / 3 = 0.666
- 没有找到回收元素且当前元素数量 > 阈值, 则扩容 https://zhuanlan.zhihu.com/p/402281916
- 内存泄露
- Map 底层是 Entry, 键为 ThreadLocal 实例弱引用
- 为什么是弱引用: 因为一般使用线程池, 那么 Thread 就是一个长生命周期对象, 若没有显式调用 remove, 则即便 ThreadLocal 实例不使用也无法被回收
- ThreadLocal 在每次 set、get 赋值都会移除被回收的对象
-- 问题: 线程池中, 一次使用了 ThreadLocal, 但是后面就不再使用了, 就产生了内存泄露
- set、get、remove 已经见减少了内存泄露的可能性
- 但是若 static 的 ThreadLocal, 则延长了生命周期
- 或者说使用了 ThreadLocal, 后续又不再调用 set、get
- 解决: 每一次使用完毕都 remove

posted @ 2022-04-07 20:25  YangDanMua  阅读(35)  评论(0编辑  收藏  举报