Skip to content

Commit 5d08dd3

Browse files
committed
集合框架
1 parent 8c3200c commit 5d08dd3

27 files changed

+1184
-3
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@
146146
- [海康威视一面:Java中Iterator和Iterable有什么区别?](docs/collection/iterator-iterable.md)
147147
- [为什么阿里巴巴强制不要在foreach里执行删除操作?还不是因为fail-fast](docs/collection/fail-fast.md)
148148
- [Java HashMap详解(附源码分析)](docs/collection/hashmap.md)
149+
- [Java LinkedHashMap详解(附源码分析)](docs/collection/linkedhashmap.md)
150+
- [Java TreeMap详解(附源码分析)](docs/collection/treemap.md)
151+
- [详解 Java 中的堆和队列(Stack and Queue 附源码分析)](docs/collection/arraydeque.md)
152+
- [详解 Java 中的优先级队列(PriorityQueue 附源码分析)](docs/collection/PriorityQueue.md)
153+
- [Java WeakHashMap详解(附源码分析)](docs/collection/WeakHashMap.md)
149154

150155
## Java输入输出
151156

docs/.vuepress/sidebar.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ export const sidebarConfig = sidebar({
134134
"collection/iterator-iterable",
135135
"collection/fail-fast",
136136
"collection/hashmap",
137+
"collection/linkedhashmap",
138+
"collection/treemap",
139+
"collection/arraydeque",
140+
"collection/PriorityQueue",
141+
"collection/WeakHashMap",
137142

138143
],
139144
},

docs/collection/PriorityQueue.md

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
---
2+
title: 详解 Java 中的优先级队列(PriorityQueue 附源码分析)
3+
shortTitle: 详解PriorityQueue
4+
category:
5+
- Java核心
6+
tag:
7+
- 集合框架(容器)
8+
description: Java程序员进阶之路,小白的零基础Java教程,详解 Java 中的优先级队列(PriorityQueue 附源码分析)
9+
head:
10+
- - meta
11+
- name: keywords
12+
content: Java,Java SE,Java 基础,Java 教程,Java 程序员进阶之路,Java 入门,Java PriorityQueue
13+
---
14+
15+
Java 中的 PriorityQueue 事通过二叉小顶堆实现的,可以用一棵完全二叉树表示。本文从 Queue 接口出发,结合生动的图解,深入浅出地分析 PriorityQueue 每个操作的具体过程和时间复杂度,让读者对 PriorityQueue 建立清晰而深入的认识。
16+
17+
## 总体介绍
18+
19+
前面以 Java [ArrayDeque](https://tobebetterjavaer.com/collection/arraydeque.html)为例讲解了*Stack**Queue*,其实还有一种特殊的队列叫做*PriorityQueue*,即优先队列。
20+
21+
**优先队列的作用是能保证每次取出的元素都是队列中权值最小的**(Java 的优先队列每次取最小元素,C++的优先队列每次取最大元素)。
22+
23+
这里牵涉到了大小关系,**元素大小的评判可以通过元素本身的自然顺序(_natural ordering_),也可以通过构造时传入的比较器**_Comparator_,类似于 C++的仿函数)。
24+
25+
Java 中*PriorityQueue*实现了*Queue*接口,不允许放入`null`元素;其通过堆实现,具体说是通过完全二叉树(_complete binary tree_)实现的**小顶堆**(任意一个非叶子节点的权值,都不大于其左右子节点的权值),也就意味着可以通过数组来作为*PriorityQueue*的底层实现。
26+
27+
![PriorityQueue_base.png](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/collection/PriorityQueue-8dca2f55-a7c7-49e1-95a5-df1a34f2aef5.png)
28+
29+
上图中我们给每个元素按照层序遍历的方式进行了编号,如果你足够细心,会发现父节点和子节点的编号是有联系的,更确切的说父子节点的编号之间有如下关系:
30+
31+
```
32+
leftNo = parentNo\*2+1
33+
34+
rightNo = parentNo\*2+2
35+
36+
parentNo = (nodeNo-1)/2
37+
```
38+
39+
通过上述三个公式,可以轻易计算出某个节点的父节点以及子节点的下标。这也就是为什么可以直接用数组来存储堆的原因。
40+
41+
*PriorityQueue*`peek()``element`操作是常数时间,`add()`, `offer()`, 无参数的`remove()`以及`poll()`方法的时间复杂度都是*log(N)*
42+
43+
## 方法剖析
44+
45+
### add()和 offer()
46+
47+
`add(E e)``offer(E e)`的语义相同,都是向优先队列中插入元素,只是`Queue`接口规定二者对插入失败时的处理不同,前者在插入失败时抛出异常,后则则会返回`false`。对于*PriorityQueue*这两个方法其实没什么差别。
48+
49+
![PriorityQueue_offer.png](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/collection/PriorityQueue-0fb89aa7-c8fa-4fad-adbb-40c61c3bb0e9.png)
50+
51+
新加入的元素可能会破坏小顶堆的性质,因此需要进行必要的调整。
52+
53+
```Java
54+
//offer(E e)
55+
public boolean offer(E e) {
56+
if (e == null)//不允许放入null元素
57+
throw new NullPointerException();
58+
modCount++;
59+
int i = size;
60+
if (i >= queue.length)
61+
grow(i + 1);//自动扩容
62+
size = i + 1;
63+
if (i == 0)//队列原来为空,这是插入的第一个元素
64+
queue[0] = e;
65+
else
66+
siftUp(i, e);//调整
67+
return true;
68+
}
69+
```
70+
71+
上述代码中,扩容函数`grow()`类似于`ArrayList`里的`grow()`函数,就是再申请一个更大的数组,并将原数组的元素复制过去,这里不再赘述。需要注意的是`siftUp(int k, E x)`方法,该方法用于插入元素`x`并维持堆的特性。
72+
73+
```Java
74+
//siftUp()
75+
private void siftUp(int k, E x) {
76+
while (k > 0) {
77+
int parent = (k - 1) >>> 1;//parentNo = (nodeNo-1)/2
78+
Object e = queue[parent];
79+
if (comparator.compare(x, (E) e) >= 0)//调用比较器的比较方法
80+
break;
81+
queue[k] = e;
82+
k = parent;
83+
}
84+
queue[k] = x;
85+
}
86+
```
87+
88+
新加入的元素`x`可能会破坏小顶堆的性质,因此需要进行调整。调整的过程为:**`k`指定的位置开始,将`x`逐层与当前点的`parent`进行比较并交换,直到满足`x >= queue[parent]`为止**。注意这里的比较可以是元素的自然顺序,也可以是依靠比较器的顺序。
89+
90+
### element()和 peek()
91+
92+
`element()``peek()`的语义完全相同,都是获取但不删除队首元素,也就是队列中权值最小的那个元素,二者唯一的区别是当方法失败时前者抛出异常,后者返回`null`。根据小顶堆的性质,堆顶那个元素就是全局最小的那个;由于堆用数组表示,根据下标关系,`0`下标处的那个元素既是堆顶元素。所以**直接返回数组`0`下标处的那个元素即可**
93+
94+
![PriorityQueue_peek.png](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/collection/PriorityQueue-5059f157-845e-4d1c-b993-5cfe539d5607.png)
95+
96+
代码也就非常简洁:
97+
98+
```Java
99+
//peek()
100+
public E peek() {
101+
if (size == 0)
102+
return null;
103+
return (E) queue[0];//0下标处的那个元素就是最小的那个
104+
}
105+
```
106+
107+
### remove()和 poll()
108+
109+
`remove()``poll()`方法的语义也完全相同,都是获取并删除队首元素,区别是当方法失败时前者抛出异常,后者返回`null`。由于删除操作会改变队列的结构,为维护小顶堆的性质,需要进行必要的调整。
110+
111+
![PriorityQueue_poll.png](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/collection/PriorityQueue-e25ba931-2e6f-4c17-84b8-9b959733d541.png)
112+
113+
代码如下:
114+
115+
```Java
116+
public E poll() {
117+
if (size == 0)
118+
return null;
119+
int s = --size;
120+
modCount++;
121+
E result = (E) queue[0];//0下标处的那个元素就是最小的那个
122+
E x = (E) queue[s];
123+
queue[s] = null;
124+
if (s != 0)
125+
siftDown(0, x);//调整
126+
return result;
127+
}
128+
```
129+
130+
上述代码首先记录`0`下标处的元素,并用最后一个元素替换`0`下标位置的元素,之后调用`siftDown()`方法对堆进行调整,最后返回原来`0`下标处的那个元素(也就是最小的那个元素)。重点是`siftDown(int k, E x)`方法,该方法的作用是**`k`指定的位置开始,将`x`逐层向下与当前点的左右孩子中较小的那个交换,直到`x`小于或等于左右孩子中的任何一个为止**
131+
132+
```Java
133+
//siftDown()
134+
private void siftDown(int k, E x) {
135+
int half = size >>> 1;
136+
while (k < half) {
137+
//首先找到左右孩子中较小的那个,记录到c里,并用child记录其下标
138+
int child = (k << 1) + 1;//leftNo = parentNo*2+1
139+
Object c = queue[child];
140+
int right = child + 1;
141+
if (right < size &&
142+
comparator.compare((E) c, (E) queue[right]) > 0)
143+
c = queue[child = right];
144+
if (comparator.compare(x, (E) c) <= 0)
145+
break;
146+
queue[k] = c;//然后用c取代原来的值
147+
k = child;
148+
}
149+
queue[k] = x;
150+
}
151+
```
152+
153+
### remove(Object o)
154+
155+
`remove(Object o)`方法用于删除队列中跟`o`相等的某一个元素(如果有多个相等,只删除一个),该方法不是*Queue*接口内的方法,而是*Collection*接口的方法。由于删除操作会改变队列结构,所以要进行调整;又由于删除元素的位置可能是任意的,所以调整过程比其它函数稍加繁琐。具体来说,`remove(Object o)`可以分为 2 种情况:1. 删除的是最后一个元素。直接删除即可,不需要调整。2. 删除的不是最后一个元素,从删除点开始以最后一个元素为参照调用一次`siftDown()`即可。此处不再赘述。
156+
157+
![PriorityQueue_remove2.png](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/collection/PriorityQueue-ed0d08d3-b38e-44a1-a710-ee7a01afda62.png)
158+
159+
具体代码如下:
160+
161+
```Java
162+
//remove(Object o)
163+
public boolean remove(Object o) {
164+
//通过遍历数组的方式找到第一个满足o.equals(queue[i])元素的下标
165+
int i = indexOf(o);
166+
if (i == -1)
167+
return false;
168+
int s = --size;
169+
if (s == i) //情况1
170+
queue[i] = null;
171+
else {
172+
E moved = (E) queue[s];
173+
queue[s] = null;
174+
siftDown(i, moved);//情况2
175+
......
176+
}
177+
return true;
178+
}
179+
```
180+
181+
> 参考链接:[https://github.com/CarpenterLee/JCFInternals](https://github.com/CarpenterLee/JCFInternals),作者:李豪,整理:沉默王二
182+
183+
184+
185+
----
186+
187+
最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
188+
189+
关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。
190+
191+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png)

docs/collection/WeakHashMap.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
---
2+
title: Java WeakHashMap详解(附源码分析)
3+
shortTitle: 详解WeakHashMap
4+
category:
5+
- Java核心
6+
tag:
7+
- 集合框架(容器)
8+
description: Java程序员进阶之路,小白的零基础Java教程,Java WeakHashMap详解(附源码分析)
9+
head:
10+
- - meta
11+
- name: keywords
12+
content: Java,Java SE,Java 基础,Java 教程,Java 程序员进阶之路,Java 入门,Java WeakHashMap
13+
---
14+
15+
16+
在Java中,我们一般都会使用到Map,比如[HashMap](https://tobebetterjavaer.com/collection/hashmap.html)这样的具体实现。更高级一点,我们可能会使用WeakHashMap。
17+
18+
WeakHashMap其实和HashMap大多数行为是一样的,只是WeakHashMap不会阻止GC回收key对象(不是value),那么WeakHashMap是怎么做到的呢,这就是我们研究的主要问题。
19+
20+
在开始WeakHashMap之前,我们先要对弱引用有一定的了解。
21+
22+
在Java中,有四种引用类型
23+
24+
* 强引用(Strong Reference),我们正常编码时默认的引用类型,强应用之所以为强,是因为如果一个对象到GC Roots强引用可到达,就可以阻止GC回收该对象
25+
* 软引用(Soft Reference)阻止GC回收的能力相对弱一些,如果是软引用可以到达,那么这个对象会停留在内存更时间上长一些。当内存不足时垃圾回收器才会回收这些软引用可到达的对象
26+
* 弱引用(WeakReference)无法阻止GC回收,如果一个对象时弱引用可到达,那么在下一个GC回收执行时,该对象就会被回收掉。
27+
* 虚引用(Phantom Reference)十分脆弱,它的唯一作用就是当其指向的对象被回收之后,自己被加入到引用队列,用作记录该引用指向的对象已被销毁
28+
29+
这其中还有一个概念叫做引用队列(Reference Queue)
30+
31+
* 一般情况下,一个对象标记为垃圾(并不代表回收了)后,会加入到引用队列。
32+
* 对于虚引用来说,它指向的对象会只有被回收后才会加入引用队列,所以可以用作记录该引用指向的对象是否回收。
33+
34+
## WeakHashMap如何不阻止对象回收呢
35+
36+
37+
```java
38+
private static final class Entry<K, V> extends WeakReference<K> implements
39+
Map.Entry<K, V> {
40+
int hash;
41+
boolean isNull;
42+
V value;
43+
Entry<K, V> next;
44+
interface Type<R, K, V> {
45+
R get(Map.Entry<K, V> entry);
46+
}
47+
Entry(K key, V object, ReferenceQueue<K> queue) {
48+
super(key, queue);
49+
isNull = key == null;
50+
hash = isNull ? 0 : key.hashCode();
51+
value = object;
52+
}
53+
```
54+
55+
56+
57+
如源码所示,
58+
59+
* WeakHashMapEntry继承了WeakReference
60+
* 其中Key作为了WeakReference指向的对象
61+
* 因此WeakHashMap利用了WeakReference的机制来实现不阻止GC回收Key
62+
63+
## 如何删除被回收的key数据呢
64+
65+
Javadoc中关于WeakHashMap有这样的描述,当key不再引用时,其对应的key/value也会被移除。
66+
67+
那么是如何移除的呢,这里我们通常有两种假设策略
68+
69+
* 当对象被回收的时候,进行通知
70+
* WeakHashMap轮询处理时效的Entry
71+
72+
WeakHashMap采用的是轮询的形式,在其put/get/size等方法调用的时候都会预先调用一个poll的方法,来检查并删除失效的Entry
73+
74+
```java
75+
void poll() {
76+
Entry<K, V> toRemove;
77+
while ((toRemove = (Entry<K, V>) referenceQueue.poll()) != null) {
78+
removeEntry(toRemove);
79+
Log.d(LOGTAG, "removeEntry=" + toRemove.value);
80+
}
81+
}
82+
```
83+
84+
85+
为什么没有使用看似更好的通知呢,我想是因为在Java中没有一个可靠的通知回调,比如大家常说的finalize方法,其实也不是标准的,不同的JVM可以实现不同,甚至是不调用这个方法。
86+
87+
当然除了单纯的看源码,进行合理的验证是检验分析正确的一个重要方法。
88+
89+
这里首先,我们定义一个MyObject类,处理一下finalize方法(在我的测试机上可以正常调用,仅仅做为辅助验证手段)
90+
91+
```java
92+
class MyObject(val id: String) : Any() {
93+
protected fun finalize() {
94+
Log.i("MainActivity", "Object($id) finalize method is called")
95+
}
96+
}
97+
```
98+
99+
100+
101+
然后是调用者的代码,如下
102+
103+
```java
104+
private val weakHashMap = WeakHashMap<Any, Int>()
105+
var count : Int = 0
106+
override fun onCreate(savedInstanceState: Bundle?) {
107+
super.onCreate(savedInstanceState)
108+
setContentView(R.layout.activity_main)
109+
setSupportActionBar(toolbar)
110+
dumpWeakInfo()
111+
fab.setOnClickListener { view ->
112+
//System.gc()// this seldom works use Android studio force gc stop
113+
weakHashMap.put(MyObject(count.toString()), count)
114+
count ++
115+
dumpWeakInfo()
116+
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
117+
.setAction("Action", null).show()
118+
}
119+
}
120+
fun dumpWeakInfo() {
121+
Log.i("MainActivity", "dumpWeakInfo weakInfo.size=${weakHashMap.size}")
122+
}
123+
```
124+
125+
126+
127+
我们按照如下操作
128+
129+
* 点击fab控件,每次对WeakhashMap对象增加一个Entry,并打印WeakHashMap的size 执行3
130+
* 在没有强制触发GC时,WeakHashMap对象size一直会增加
131+
* 手动出发Force GC,我们会看到MyObject有finalize方法被调用
132+
* 再次点击fab空间,然后输出的WeakHashMap size急剧减少。
133+
* 同样我们收到在WeakHashMap增加的日志也会输出
134+
135+
136+
```java
137+
I/MainActivity(10202): dumpWeakInfo weakInfo.size=1
138+
I/MainActivity(10202): dumpWeakInfo weakInfo.size=2
139+
I/MainActivity(10202): dumpWeakInfo weakInfo.size=3
140+
I/MainActivity(10202): Object(2) finalize method is called
141+
I/MainActivity(10202): Object(1) finalize method is called
142+
I/MainActivity(10202): Object(0) finalize method is called
143+
I/WeakHashMap(10202): removeEntry=2
144+
I/WeakHashMap(10202): removeEntry=0
145+
I/WeakHashMap(10202): removeEntry=1
146+
I/MainActivity(10202): dumpWeakInfo weakInfo.size=1
147+
```
148+
149+
150+
注意:System.gc()并不一定可以工作,建议使用Android StudioForce GC
151+
152+
完整的测试代码可以访问这里 [https://github.com/androidyue/WeakHashMapSample](https://github.com/androidyue/WeakHashMapSample)
153+
154+
155+
----
156+
157+
最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVMJava集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类SpringNetty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
158+
159+
关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。
160+
161+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png)

0 commit comments

Comments
 (0)