Skip to content

Commit 4ea2bcf

Browse files
author
jesper
committed
update Java容器类
1 parent e8b81e1 commit 4ea2bcf

File tree

5 files changed

+124
-50
lines changed

5 files changed

+124
-50
lines changed

notes/java/Java容器.md

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -208,49 +208,50 @@ Java 8的ConcurrentHashMap同样是通过Key的哈希值与数组长度取模确
208208

209209
## HashMap原理
210210
### HashMap特性
211-
  HashMap的特性:HashMap存储键值对,实现快速存取数据;允许null键/值;非同步;不保证有序(比如插入的顺序)。实现map接口。
211+
HashMap的特性:HashMap存储键值对,实现快速存取数据;允许null键/值;非同步;不保证有序(比如插入的顺序)。实现map接口。
212212

213213
### HashMap的原理,内部数据结构
214-
  HashMap是基于hashing的原理,底层使用哈希表(数组 + 链表)实现。里边最重要的两个方法put、get,使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。
215-
  存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。
214+
HashMap是基于hashing的原理,底层使用哈希表(数组 + 链表)实现。里边最重要的两个方法put、get,使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。
215+
216+
存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。
216217

217218
### 讲一下 HashMap 中 put 方法过程
218-
1.对key的hashCode做hash操作,然后再计算在bucket中的index(1.5 HashMap的哈希函数);
219-
2.如果没碰撞直接放到bucket里;
220-
3.如果碰撞了,以链表的形式存在buckets后;
221-
4.如果节点已经存在就替换old value(保证key的唯一性)
222-
5.如果bucket满了(超过阈值,阈值=loadfactor*current capacity,load factor默认0.75),就要resize。
219+
1. 对key的hashCode做hash操作,然后再计算在bucket中的index(1.5 HashMap的哈希函数);
220+
2. 如果没碰撞直接放到bucket里;
221+
3. 如果碰撞了,以链表的形式存在buckets后;
222+
4. 如果节点已经存在就替换old value(保证key的唯一性)
223+
5. 如果bucket满了(超过阈值,阈值=loadfactor*current capacity,load factor默认0.75),就要resize。
223224

224225
### get()方法的工作原理
225-
  通过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而获得buckets的位置。如果产生碰撞,则利用key.equals()方法去链表中查找对应的节点。
226+
通过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而获得buckets的位置。如果产生碰撞,则利用key.equals()方法去链表中查找对应的节点。
226227

227228
### HashMap中hash函数怎么是是实现的?还有哪些 hash 的实现方式?
228-
  1. 对key的hashCode做hash操作(高16bit不变,低16bit和高16bit做了一个异或);
229-
  2. h & (length-1); //通过位操作得到下标index。
229+
1. 对key的hashCode做hash操作(高16bit不变,低16bit和高16bit做了一个异或);
230+
2. h & (length-1); //通过位操作得到下标index。
230231

231-
  还有数字分析法、平方取中法、分段叠加法、 除留余数法、 伪随机数法。
232+
还有数字分析法、平方取中法、分段叠加法、 除留余数法、 伪随机数法。
232233

233234
### HashMap 怎样解决冲突?
234-
  HashMap中处理冲突的方法实际就是链地址法,内部数据结构是数组+单链表。
235+
HashMap中处理冲突的方法实际就是链地址法,内部数据结构是数组+单链表。
235236

236237
#### 扩展问题1:当两个对象的hashcode相同会发生什么?
237-
  因为两个对象的Hashcode相同,所以它们的bucket位置相同,会发生“碰撞”。HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。
238+
因为两个对象的Hashcode相同,所以它们的bucket位置相同,会发生“碰撞”。HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。
238239

239240
#### 扩展问题2:抛开 HashMap,hash 冲突有那些解决办法?
240-
  开放定址法、链地址法、再哈希法。
241+
开放定址法、链地址法、再哈希法。
241242

242243
### 如果两个键的hashcode相同,你如何获取值对象?
243-
  重点在于理解hashCode()与equals()。
244-
  通过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而获得buckets的位置。两个键的hashcode相同会产生碰撞,则利用key.equals()方法去链表或树(java1.8)中去查找对应的节点。
244+
重点在于理解hashCode()与equals()。
245+
246+
通过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而获得buckets的位置。两个键的hashcode相同会产生碰撞,则利用key.equals()方法去链表或树(java1.8)中去查找对应的节点。
245247

246248
### 针对 HashMap 中某个 Entry 链太长,查找的时间复杂度可能达到 O(n),怎么优化?
247-
  将链表转为红黑树,实现 O(logn) 时间复杂度内查找。JDK1.8 已经实现了。
249+
将链表转为红黑树,实现 O(logn) 时间复杂度内查找。JDK1.8 已经实现了。
248250

249251
### 如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
250-
  扩容。这个过程也叫作rehashing,因为它重建内部数据结构,并调用hash方法找到新的bucket位置。
251-
  大致分两步:
252-
  1.扩容:容量扩充为原来的两倍(2 * table.length);
253-
  2.移动:对每个节点重新计算哈希值,重新计算每个元素在数组中的位置,将原来的元素移动到新的哈希表中。
252+
扩容。这个过程也叫作rehashing,因为它重建内部数据结构,并调用hash方法找到新的bucket位置。 大致分两步:
253+
1. 扩容:容量扩充为原来的两倍(2 * table.length);
254+
2. 移动:对每个节点重新计算哈希值,重新计算每个元素在数组中的位置,将原来的元素移动到新的哈希表中。
254255

255256
补充:
256257
1. loadFactor:加载因子。默认值DEFAULT_LOAD_FACTOR = 0.75f;
@@ -269,13 +270,13 @@ Java 8的ConcurrentHashMap同样是通过Key的哈希值与数组长度取模确
269270
  Hashtable可以看做是线程安全版的HashMap,两者几乎“等价”(当然还是有很多不同)。Hashtable几乎在每个方法上都加上synchronized(同步锁),实现线程安全。
270271

271272
### 区别
272-
  1.HashMap继承于AbstractMap,而Hashtable继承于Dictionary;
273-
  2.线程安全不同Hashtable的几乎所有函数都是同步的,即它是线程安全的,支持多线程。而HashMap的函数则是非同步的,它不是线程安全的。若要在多线程中使用HashMap,需要我们额外的进行同步处理;
274-
  3.null值HashMap的key、value都可以为null。Hashtable的key、value都不可以为null;
275-
  4.迭代器(Iterator)HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException。
276-
  5.容量的初始值和增加方式都不一样:HashMap默认的容量大小是16;增加容量时,每次将容量变为“原始容量x2”。Hashtable默认的容量大小是11;增加容量时,每次将容量变为“原始容量x2 + 1”;
277-
  6.添加key-value时的hash值算法不同:HashMap添加元素时,是使用自定义的哈希算法。Hashtable没有自定义哈希算法,而直接采用的key的hashCode()。
278-
  7.速度。由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
273+
1. HashMap继承于AbstractMap,而Hashtable继承于Dictionary;
274+
2. 线程安全不同Hashtable的几乎所有函数都是同步的,即它是线程安全的,支持多线程。而HashMap的函数则是非同步的,它不是线程安全的。若要在多线程中使用HashMap,需要我们额外的进行同步处理;
275+
3. null值HashMap的key、value都可以为null。Hashtable的key、value都不可以为null;
276+
4. 迭代器(Iterator)HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException。
277+
5. 容量的初始值和增加方式都不一样:HashMap默认的容量大小是16;增加容量时,每次将容量变为“原始容量x2”。Hashtable默认的容量大小是11;增加容量时,每次将容量变为“原始容量x2 + 1”;
278+
6. 添加key-value时的hash值算法不同:HashMap添加元素时,是使用自定义的哈希算法。Hashtable没有自定义哈希算法,而直接采用的key的hashCode()。
279+
7. 速度。由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
279280

280281
### 能否让HashMap同步?
281282
  HashMap可以通过下面的语句进行同步:Map m = Collections.synchronizeMap(hashMap);

notes/java/Java并发.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,17 @@
4848
4. 阻塞状态(Blocked) : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
4949

5050
1、等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
51+
5152
2、同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
53+
5254
3、 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5355

5456
5. 死亡状态(Dead) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
5557

5658
1、run()或者call()方法执行完成,线程正常结束;
59+
5760
2、线程抛出一个未捕获的Exception或Error;
61+
5862
3、直接调用该线程的stop()方法来结束该线程;
5963

6064
## synchronized 的底层怎么实现

src/io/nio/ByteBufferTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.nio;
2+
3+
import sun.nio.ch.DirectBuffer;
4+
5+
import java.nio.ByteBuffer;
6+
7+
/**
8+
* @Auther: Jesper
9+
* @Date: 2019/1/23 09:10
10+
* @Description:
11+
*/
12+
public class ByteBufferTest {
13+
14+
public static void main(String[] args) {
15+
//这两个方法都是实例化HeapByteBuffer来创建的ByteBuffer对象
16+
ByteBuffer allocate = ByteBuffer.allocate(10);
17+
ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[]{});
18+
19+
ByteBuffer byteBuffer1 = ByteBuffer.allocateDirect(10);
20+
21+
System.out.println(allocate);
22+
23+
}
24+
25+
public static void clean(final ByteBuffer byteBuffer){
26+
if (byteBuffer.isDirect()){
27+
((DirectBuffer)byteBuffer).cleaner().clean();
28+
}
29+
}
30+
}

src/jdk/agent/AttachTest.java

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package pool;
2+
3+
4+
import java.util.Date;
5+
import java.util.concurrent.ScheduledExecutorService;
6+
import java.util.concurrent.ScheduledThreadPoolExecutor;
7+
import java.util.concurrent.TimeUnit;
8+
9+
/**
10+
* @Auther: Jesper
11+
* @Date: 2019/1/24 10:20
12+
* @Description:
13+
*/
14+
public class ScheduledThreadPoolTest {
15+
16+
public static void main(String[] args) throws InterruptedException {
17+
//创建大小为5的线程池
18+
ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(5);
19+
20+
for (int i = 0; i < 3; i++) {
21+
Task worker = new Task("task-" + i);
22+
//周期性执行,每5秒执行一次
23+
scheduledExecutorService.scheduleAtFixedRate(worker, 0, 5, TimeUnit.SECONDS);
24+
25+
}
26+
Thread.sleep(10000);
27+
28+
System.out.println("Shutting down executor...");
29+
30+
scheduledExecutorService.shutdown();
31+
boolean isDone;
32+
33+
do {
34+
isDone = scheduledExecutorService.awaitTermination(1, TimeUnit.DAYS);
35+
System.out.println("awaitTermination...");
36+
} while (!isDone);
37+
38+
System.out.println("Finished all threads");
39+
}
40+
}
41+
42+
class Task implements Runnable {
43+
44+
private String name;
45+
46+
public Task(String name) {
47+
this.name = name;
48+
}
49+
50+
@Override
51+
public void run() {
52+
System.out.println("name = " + name + ", startTime = " + new Date());
53+
try {
54+
Thread.sleep(1000);
55+
} catch (InterruptedException e) {
56+
e.printStackTrace();
57+
}
58+
System.out.println("name = " + name + ", endTime = " + new Date());
59+
}
60+
}

0 commit comments

Comments
 (0)