我是靠谱客的博主 还单身自行车,这篇文章主要介绍Java关于HashMap的总结自定义HashMap,现在分享给大家,希望可以做个参考。

Java关于HashMap的总结

Map集合 存储键值对,不能有重复的key,每一个key对应一个value;

哈希表

散列表,是根据关键码值(key)进行访问的数据结构,也就是说,通过将key映射到表中一个位置来获取记录,加快查找的速度,这个映射函数叫做散列函数,存放记录的结构称之为散列表;寻址容易,插入删除也容易的数据结构;
链表的时间复杂度为O(N),二叉排序树的时间复杂度为O(log2 N)
散列表可以根据key来找到value,时间复杂度达到O(1)
key采用hash函数来定位,通过hash函数可以得到散列码值;
通常以下几种方式构造哈希函数
(1)直接定址法:key address(key) = a*key+b;
(2)平方取中法:给key值平方,取中间两位作为哈希函数的散了码
(3)折叠法:把key拆分为几部分,例如图书馆里的书,拆分为区域号-书架号-图书编号,这几个号码做一些运算作为散列码
(4)除留取余法:key作为被除数,hash表对应的最大长度m,取不大于m的质数作为除数,key%质数作为散列码;

哈希冲突

如果哈希函数构造的不好,容易产生哈希冲突,使得时间复杂度从O(1)升为O(N)或更大。

解决哈希冲突:

(1)开放地址法:当插入元素且得到两个散列码相同时,第一个元素插入后,第二个元素插入到它后面的位置。
(2)链地址法:当插入元素且得到两个元素散列码相同时,第一个元素插入后,在这个元素的位置加入一个链表,插入第二个元素;hashmap采用链地址法解决hash冲突。

HashMap的使用

hashmap是基于哈希表非同步实现的,哈希表对应的接口是Map接口(非线程安全),我们可以通过key和hash函数得到散列码,从而定位位置,进行增删改查的操作。
jdk1.8之前,HashMap都是采用数组+链表实现的,即使用链地址法处理哈希冲突;不同的key值可能得到同样的散列码,同一Hash值的节点都存储在一个链表中,但是当链表中的元素越来越多的时候;通过key去查找的效率反而从O(1)~O(N)。jkd1.8中,HashMap采用数组+链表+红黑树的结构实现,当前链表的长度超过阈值8,将链表转换为红黑树,红黑树中的元素个数小于6,将红黑树再转换为链表。

自定义HashMap

hash算法直接使用源码中的hash算法,解决哈希冲突采用链地址法,即数组+链表实现,包括方法有put(K key,V value),get(K key),remove(K key)等方法
HashMap迭代器实现
(1)哈希表中数据分布是不连续的,在迭代器初始化的过程中必须先跳转到第一个非空数据节点
(2)当迭代器的下标到达当前桶的链表末尾时,迭代器下标跳转到下一个非空桶的第一个数据节点

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
class MyHashMap<K,V>{ private Node<K, V>[] table; private int size; class Node<K,V>{ protected K key; protected V value; protected int hash; protected Node<K, V> next; public Node(K key, V value, int hash) { this.key = key; this.value = value; this.hash = hash; } } public MyHashMap(int capacity){ table = new Node[capacity]; } public Iterator<Node<K, V>> iterator(){ return new Itr(); } class Itr implements Iterator<Node<K, V>>{ private int currentIndex;//当前桶的位置 private Node<K, V> nextNode;//返回的下一个数据节点 private Node<K, V> currentNode; //上一次next返回的数据节点 public Itr(){ if(MyHashMap.this.isEmpty()){ return; } for(int i=0; i < table.length; i++){ currentIndex = i; Node<K, V> firstNode = table[i]; if(firstNode != null){ nextNode = firstNode; return; } } } @Override public boolean hasNext() { return nextNode != null; } @Override public Node<K, V> next() { //返回下一个数据节点 currentNode = nextNode; //nextNode指向自己的next nextNode = nextNode.next; if(nextNode == null){ //说明当前桶的链表已经遍历完毕 //寻找下一个非空的桶 for(int i=currentIndex+1; i<MyHashMap.this.table.length; i++){ //设置当前桶位置 currentIndex = i; Node<K,V> firstNode = MyHashMap.this.table[currentIndex]; if(firstNode != null){ nextNode = firstNode; break; } } //nextNode保存的就是下一个非空的数据节点 } return currentNode; } @Override public void remove() { if(currentNode == null){ return; } K currentKey = currentNode.key; MyHashMap.this.remove(currentKey); currentNode = null; } } public void resize(){ //扩容桶table Node<K, V>[] newTable = new Node[table.length * 2]; for(int i=0; i<table.length; i++){ //将oldTable中每一个位置映射到newTable中 rehash(i, newTable); } this.table = newTable; } public void rehash(int index, Node<K,V>[] newTable){ Node<K, V> currentNode = table[index]; if(currentNode == null){ return; } Node<K, V> lowListHead = null; //低位的头 Node<K, V> lowListTail = null; //低位的尾 Node<K, V> highListHead = null; //高位的头 Node<K, V> highListTail = null; //高位的尾 //currentNode表示oleTable下index位置中当前节点 while(currentNode != null){ //当前节点在newTable中的位置 int newIndex = newTable.length-1 & hash(currentNode.key); if(newIndex == index){ //映射到原先下标处 if(lowListHead == null){ lowListHead = currentNode; lowListTail = currentNode; }else{ lowListTail.next = currentNode; lowListTail = lowListTail.next; } }else{ //newIndex与index不等,映射到高位下标处 if(highListHead == null){ highListHead = currentNode; highListTail = currentNode; }else{ highListTail.next = currentNode; highListTail = lowListTail.next; } } currentNode = currentNode.next; } //将lowList head->tail之前的节点链接到index位置 if(lowListTail != null){ newTable[index] = lowListHead; lowListHead.next = null; } //将highList head->tail之前的节点链接到index+table.length位置 if(highListTail != null){ newTable[index+this.table.length] = highListHead; highListHead.next = null; } } public int hash(Object key) { int h; return (h = key.hashCode()) ^ (h >>> 16); } public void put(K key, V value){ //确定所要添加元素的位置 int hash = hash(key);//散列码 int index = table.length-1 & hash;//确定的位置 //newNode -》table[index] == null || key在map不存在 //table[index]已经存在数据 //table[index]不存在数据 Node<K, V> firstNode = table[index];//得到该位置的第一个节点 if(firstNode == null){ table[index] = new Node(key, value, hash); size++; }else{ //第一种情况 key已经存在 考虑新值覆盖旧值 //第二种情况 key不存在 封装一个新的node尾插法插入链表 if(firstNode.key.equals(key)){ firstNode.value = value; //新值替换旧值 }else{ Node<K,V> tmp = firstNode; while(tmp.next != null && !tmp.key.equals(key)){ tmp = tmp.next; } if(tmp.next == null){ if(tmp.key.equals(key)){ tmp.value = value; //新值替换旧值 }else{ tmp.next = new Node(key, value, hash); size++; } }else{ tmp.value = value; } } } } public boolean remove(K key){ int hash = hash(key); int index = table.length-1 & hash; Node<K, V> firstNode = table[index]; if(firstNode == null){ return false; }else{ if(firstNode.next == null){ if(firstNode.key.equals(key) && firstNode.hash == hash){ //为什么判断key是否相等的同时还需要判断散列码 table[index] = null; return true; } } Node<K,V> tmpPrev = firstNode; Node<K, V> tmp = firstNode.next; while(tmp.next != null){ if(tmp.key.equals(key) && tmp.hash == hash){ //tmp之前节点的next指向tmp.next tmpPrev.next = tmp.next; size--; }else{ tmpPrev = tmp; tmp = tmp.next; } } } return false; } public Node<K,V> get(K key){ //获取对应key所在数组的index int hash = hash(key);//散列码 int index = table.length - 1 & hash; //定位key记录所在的位置 Node<K, V> firstNode = table[index]; if (firstNode == null) { return null; } if (hash == firstNode.hash && key == firstNode.key) { return firstNode; } else { //遍历链表 Node<K, V> currentNode = firstNode.next; while (currentNode != null) { if (currentNode.hash == hash && currentNode.key == key) { return currentNode; } currentNode = currentNode.next; } } return null; } public boolean isEmpty(){ return size == 0; } } public class TestDemo11 { public static void main(String[] args) { } }

源码

以putvalue为例

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
* static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 16 * static final int MAXIMUM_CAPACITY = 1 << 30; * static final float DEFAULT_LOAD_FACTOR = 0.75f; * static final int TREEIFY_THRESHOLD = 8; 桶上的节点个数大于8时会转为红黑树 * static final int UNTREEIFY_THRESHOLD = 6; 桶上的绩点个数小于6时会转为链表 * static final int MIN_TREEIFY_CAPACITY = 64; 桶中结构转化为红黑树对用的table的最小大小 * transient Node<K,V>[] table; 存储元素的数组 总是2的幂次方 * transient Set<Map.Entry<K,V>> entrySet; 哈希表中节点的集合 * transient int size; * transient int modCount; * int threshold; 临界值 * final float loadFactor; final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }

HashMap和HashTabl的区别

HashMapHashTable
继承的父类不同DictionaryAbstractMap
默认容量1116
Table的初始化时机构造函数中初始化第一次put
并发操作使用同步机制,实际应用程序中,仅仅是Hashtable本身的同步并不能保证程序在并发操作下的正确性,需要高层次的并发保护。没有同步机制,需要使用者自己进行并发访问控制
数据遍历的方式Iterator和EnumerationIterator
是否支持fast-fail用Iterator遍历,支持fast-fail 用Enumeration不支持fast-fail支持fast-fail
是否接受值为null的Key或Value不接受接受
根据Hash值计算数组下标的算法当数组长度较小,并且Key的hash值低位数值分散不均匀时,不同的hash值计算得到相同下标值的几率较高优于hashtable,通过对Key的hash做移位运算和位的与运算,使其能更广泛地分散到数组的不同位置
Entry数组的长度缺省初始长度为11,初始化时可以指定initial capacity缺省初始长度为16,长度始终保持2的n次方初始化时可以指定initial capacity的2n次方值作为其初始长度
LoadFactor负荷因子0.750.75
负荷超过(loadFactor*数组长度时),内部数据的调整方式扩展数组:2*原数组长度+1扩展数组:原数组长度*2
两者都会重新根据Key的hash值计算其在数组中的新位置,重新放置。算法相似,时间、空间效率相同

weakHashMap是特殊HashMap
WeakHashMap的键关联的对象是弱引用对象

复制代码
1
2
static class Entyr<K,V>extends WeakReference<Object>Entry 继承自WeakHashMap

Java中四种引用
1.强引用
通过new出来对象所关联的引用称之为强引用,只要强引用存在,当前对象不会被回收
2.软引用
通过SoftReference类实现,在系统内存即将要溢出的时候,才会回收软引用对象
3.弱引用
通过WeakReference实现,只要发生垃圾回收,被弱引用关联的对象就会被回收掉
4.虚引用
通过PhantomReference实现,无法通过虚引用获取对象实例,唯一作用就是在这个对象被回收时,能够收到一个通知

最后

以上就是还单身自行车最近收集整理的关于Java关于HashMap的总结自定义HashMap的全部内容,更多相关Java关于HashMap内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(87)

评论列表共有 0 条评论

立即
投稿
返回
顶部