文章提交
This commit is contained in:
25
src/content/posts/Java/集合/ArrayList和LinkedList的区别.md
Normal file
25
src/content/posts/Java/集合/ArrayList和LinkedList的区别.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: ArrayList和LinkedList的区别
|
||||
published: 2025-08-06
|
||||
description: ''
|
||||
image: ''
|
||||
tags: ['ArrayList', 'LinkedList']
|
||||
category: 'Java > 集合框架'
|
||||
draft: false
|
||||
lang: ''
|
||||
---
|
||||
|
||||
|
||||
# Java ArrayList和LinkedList的区别
|
||||
|
||||
ArrayList基于动态数组实现,LinkedList基于双向链表实现,这是它们所有性能差异的根本原因
|
||||
|
||||
ArrayList随机访问是O(1),但是中间插入是O(n),LinkedList则相反,随机访问是O(n),但在已知位置的插入删除是O(1)
|
||||
|
||||
LinkedList由于要存储前后节点的引用,每个元素的内存开销更大,ArrayList更节省内存,但可能因为扩容机制造成一定的浪费。
|
||||
|
||||
# 实际应用场景
|
||||
|
||||
在实际项目中,如果需要频繁随机访问元素,会选择ArrayList,如果需要频繁在两端添加删除元素,比如实现队列和栈,我会选择LinkedList
|
||||
|
||||
|
||||
40
src/content/posts/Java/集合/JavaHashMap为什么在jdk8引入红黑树.md
Normal file
40
src/content/posts/Java/集合/JavaHashMap为什么在jdk8引入红黑树.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Java HashMap为什么在jdk8引入红黑树
|
||||
published: 2025-08-05
|
||||
description: ''
|
||||
image: ''
|
||||
tags: ['红黑树','HashMap']
|
||||
category: 'Java > 集合框架'
|
||||
draft: false
|
||||
lang: ''
|
||||
---
|
||||
|
||||
|
||||
# Java HashMap为什么在jdk8引入红黑树
|
||||
|
||||
在JDK8之前,HashMap的内部实现主要依赖于数组+链表的结构
|
||||
当多个元素的哈希值相同的时候(也就是发生哈希冲突的时候),这些元素会被存储在同一个桶里面,形成一个链表。
|
||||
但这种实现方式在特定情况下会导致性能问题。
|
||||
|
||||
# JDK8之前的问题
|
||||
|
||||
1. 时间复杂度退化: 在最坏情况下(大量元素哈希到同一个桶),查找,插入和删除操作的时间复杂度会从理想的O(1)退化为O(n),其中n是链表的长度
|
||||
2. 哈希冲突攻击: 恶意攻击者可以构造大量哈希冲突的数据,使得HashMap的性能急剧下降,导致潜在的拒接服务攻击。
|
||||
|
||||
# 红黑树的引入
|
||||
JDK8对HashMap进行了优化,引入了红黑树来解决上面的问题:
|
||||
- 性能提升: 当一个桶中的元素数量超过一定阈值的时候,链表会被转换成红黑树。红黑树是一种自平衡的二叉搜索树,即使在最坏的情况下,它查找,插入和删除操作的时间复杂度也能保持在O(logn),大大提高了性能。
|
||||
|
||||
- 阈值机制:
|
||||
- 当桶中元素超过8个的时候,链表转换为红黑树
|
||||
- 当桶中元素少于6个的时候,红黑树会退化回链表
|
||||
|
||||
- 安全性增强: 通过引入红黑树,即使面对哈希冲突的攻击,HashMap也能保持相对稳定的性能,提高系统安全性
|
||||
|
||||
|
||||
# 为什么选择红黑树
|
||||
平衡性: 红黑树是一种近似平衡的二叉搜索树,能保证最坏情况下的O(logn)的性能。
|
||||
|
||||
内存占用: 相比AVL树等其它平衡树,红黑树的平衡条件较为宽松,旋转操作更少,内存占用更小。
|
||||
|
||||
实现更好的复杂度与性能平衡
|
||||
14
src/content/posts/Java/集合/Java迭代器Iterator和Iterable.md
Normal file
14
src/content/posts/Java/集合/Java迭代器Iterator和Iterable.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Java迭代器Iterator和Iterable
|
||||
published: 2025-08-05
|
||||
description: ''
|
||||
image: ''
|
||||
tags: [Iterator,Iterable]
|
||||
category: 'Java > 集合框架'
|
||||
draft: false
|
||||
lang: ''
|
||||
---
|
||||
|
||||
# Java迭代器Iterator和Iterable
|
||||
|
||||
|
||||
69
src/content/posts/Java/集合/concurrenthashmap的实现原理.md
Normal file
69
src/content/posts/Java/集合/concurrenthashmap的实现原理.md
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: concurrenthashmap的实现原理
|
||||
published: 2025-08-06
|
||||
description: ''
|
||||
image: ''
|
||||
tags: [ConcurrentHashMap,Java]
|
||||
category: 'Java > 集合框架'
|
||||
draft: false
|
||||
lang: ''
|
||||
---
|
||||
|
||||
|
||||
# ConcurrentHashMap实现原理
|
||||
|
||||
ConcurrentHashMap是Java并发包中一种线程安全的哈希表实现。
|
||||
HashMap在多线程环境下扩容会出现CPU接近100%的情况,因为HashMap并不是线程安全的,我们可以通过Collections里面的Map<K,V> synchronizedMap(Map<K,V> m) 把HashMap包装成一个线程安全的map
|
||||
|
||||
比如SynchronizedMap的put方法就是加锁过的
|
||||
|
||||
|
||||
# ConcurrentHashMap的变化
|
||||
ConcurrentHashMap在JDK1.7中,提供了一种粒度更细的加锁机制,这种机制叫分段锁,整个哈希表被分为多个段,每个段都独立锁定。读取操作不需要锁,写入操作仅锁定相关的段,这减小了锁冲突的几率,提高了并发性能。
|
||||
|
||||
这种机制的优点是: 在并发环境下将实现更高的吞吐量,在单线程环境下只损失非常小的性能。
|
||||
|
||||
可以这样理解分段锁,就是将数据分段,对每一段数据分配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
|
||||
|
||||
有些方法需要跨段,比如size(),isEmpty(),containsValue(),它们可能需要锁定整个表而不仅仅是某个段,这需要按顺序锁定所有段,操作完以后,再按顺序释放所有段的锁。
|
||||
|
||||
|
||||
ConcurrentHashMap是由Segment数组结构和HashEntry构成的,Segment是一种可重入的锁,HashEntry则用于存储键值对数据。
|
||||
|
||||
一个ConcurrentHashMap里面包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构,一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护着一个HashEntry数组里的元素,当HashEntry数组的数据进行修改的时候,必须首先获得它对应的Segment锁。
|
||||
|
||||
在外部:有一个 Segment 数组,作为并发控制的“总入口”,每个 Segment 都是一个独立的锁喵~
|
||||
在内部:每个 Segment 自己就是一个完整的小型 HashMap!它有自己的哈希表数组,里面的每个桶都可以通过 next 指针挂着一个或多个 Entry 组成的链表.
|
||||
|
||||
|
||||
|
||||
# ConcurrentHashMap 读写过程
|
||||
|
||||
## get方法
|
||||
- 为输入的key做hash运算,得到hash值
|
||||
- 通过Hash值,定位到对应的Segment对象
|
||||
- 再次通过hash值,定位到Segment当中数组的具体位置
|
||||
|
||||
|
||||
## put方法
|
||||
- 为输入的key做hash运算,得到hash值
|
||||
- 通过hash值,定位到对应的Segment对象
|
||||
- 获取可重入锁
|
||||
- 再次通过hash值,定位到Segment当中数组的具体位置
|
||||
- 插入或者覆盖HashEntry对象
|
||||
- 释放锁
|
||||
|
||||
|
||||
# JDK1.8
|
||||
在JDK1.8中,ConcurrentHashMap主要做了两个优化:
|
||||
- 和HashMap一样,链表也会在长度到达8的时候转换为红黑树,这样可以提升大量冲突的时候的查询效率。
|
||||
- 以某个位置的头结点为锁,配合自旋 + CAS 避免不必要的锁开销,进一步提升并发性能。
|
||||
- 相比JDK1.7中的ConcurrentHashMap,JDK1.8的ConcurrentHashMap取消了Segment分段锁,采用CAS + synchronized来保证并发安全性。整个容器只分为一个Segment,也就是table数组。
|
||||
- JDK1.8中的ConcurrentHashMap对节点Node类中的共享变量,和JDK1.7一样,使用volatile关键字,保证多线程操作的时候,变量的可见性。
|
||||
|
||||
|
||||
# ConcurrentHashMap的字段
|
||||
1. table
|
||||
这个装载Node的数组,作为ConcurrentHashMap的底层容器,采用加载的方式,直到第一次插入数据的时候才会进行初始化操作
|
||||
数组的大小是2的幂次方。
|
||||
|
||||
Reference in New Issue
Block a user