首页
Web开发
Windows程序
编程语言
数据库
移动开发
系统相关
微信
其他好文
会员
首页
>
其他好文
> 详细
HashMap底层实现(源码分析)
时间:
2015-07-20 11:06:33
阅读:
447
评论:
0
收藏:
0
[点我收藏+]
标签:
一、数据结构
Map将实际数据存储在Entry类的数组中。
代码片段:
Java代码
transient
Entry[] table;
//HashMap的成员变量,存放数据
static
class
Entry<K,V>
implements
Map.Entry<K,V> {
//内部类Entry
final
K key;
V value;
Entry<K,V> next;
//指向下一个数据
final
int
hash;
/**
* Creates new entry.
*/
Entry(
int
h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
执行下面代码后,可能的存储内部结构是:
Map map = new HashMap();
map.put("key1","value1");
map.put("key2","value2");
map.put("key3","value3");
执行put方法时根据key的hash值来计算放到table数组的下标,如果hash到相同的下标,则新put进去的元素放到Entry链的头部,如上图所示。put方法的源码后面详细解释。
二、属性和构造方法
Java代码
static
final
int
DEFAULT_INITIAL_CAPACITY =
16
;
//默认的初始大小,如果执行无参的构造方法,则默认初始大小为16
static
final
int
MAXIMUM_CAPACITY =
1
<<
30
;
//最大容量,1073741824
static
final
float
DEFAULT_LOAD_FACTOR =
0
.75f;
//默认的负载因子,如果没有通过构造方法传入负载因子,则使用0.75。
transient
Entry[] table;
//存放具体键值对的Entry数组
transient
int
size;
//HashMap的大小
int
threshold;
//阀值 threshold = (int)(capacity * loadFactor); 即容量*负载因子,执行put方法时如果size大于threshold则进行扩容,后面put方法将会看到
final
float
loadFactor;
//用户设置的负载因子
transient
volatile
int
modCount;
//HashMap实例被改变的次数,这个同ArrayList
构造方法一、
Java代码
public
HashMap() {
this
.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (
int
)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table =
new
Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
使用了默认的容量和默认的负载因子。
构造方法二、
Java代码
public
HashMap(
int
initialCapacity) {
this
(initialCapacity, DEFAULT_LOAD_FACTOR);
}
使用了用户设置的初始容量和默认的负载因子。
构造方法三、
Java代码
public
HashMap(
int
initialCapacity,
float
loadFactor) {
if
(initialCapacity <
0
)
throw
new
IllegalArgumentException(
"Illegal initial capacity: "
+
initialCapacity);
if
(initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if
(loadFactor <=
0
|| Float.isNaN(loadFactor))
throw
new
IllegalArgumentException(
"Illegal load factor: "
+
loadFactor);
// Find a power of 2 >= initialCapacity
int
capacity =
1
;
while
(capacity < initialCapacity)
capacity <<=
1
;
this
.loadFactor = loadFactor;
threshold = (
int
)(capacity * loadFactor);
table =
new
Entry[capacity];
init();
}
用户传入了初始容量和负载因子,这两个值是HashMap性能优化的关键,涉及到了HashMap的扩容问题。
HashMap的容量永远是2的倍数,如果传入的不是2的倍数则被调整为大于传入值的最近的2的倍数,例如如果传入130,则capacity计算后是256。是这段代码起的作用:
Java代码
while
(capacity < initialCapacity)
capacity <<=
1
;
构造方法四、
Java代码
public
HashMap(Map<?
extends
K, ?
extends
V> m) {
this
(Math.max((
int
) (m.size() / DEFAULT_LOAD_FACTOR) +
1
,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
//计算Map的大小
putAllForCreate(m);
//初始化
}
private
void
putAllForCreate(Map<?
extends
K, ?
extends
V> m) {
for
(Iterator<?
extends
Map.Entry<?
extends
K, ?
extends
V>> i = m.entrySet().iterator(); i.hasNext(); ) {
//通过entryset进行遍历
Map.Entry<?
extends
K, ?
extends
V> e = i.next();
putForCreate(e.getKey(), e.getValue());
}
}
根据传入的map进行初始化。
三、关键方法
1)put
Java代码
public
V put(K key, V value) {
if
(key ==
null
)
return
putForNullKey(value);
//单独处理,总是放到table[0]中
int
hash = hash(key.hashCode());
//计算key的hash值,后面介绍性能的时候再说这个hash方法。
int
i = indexFor(hash, table.length);
//将hash和length-1取&来得到数组的下表
for
(Entry<K,V> e = table[i]; e !=
null
; e = e.next) {
Object k;
if
(e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(
this
);
return
oldValue;
}
}
//如果这个key值,原来已经则替换后直接返回。
modCount++;
addEntry(hash, key, value, i);
return
null
;
}
void
addEntry(
int
hash, K key, V value,
int
bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] =
new
Entry<K,V>(hash, key, value, e);
//如果table[bucketIndex]中已经存在Entry则放到头部。
if
(size++ >= threshold)
//如果大于了阀值,则扩容到原来大小的2倍。
resize(
2
* table.length);
}
void
resize(
int
newCapacity) {
Entry[] oldTable = table;
int
oldCapacity = oldTable.length;
if
(oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return
;
}
Entry[] newTable =
new
Entry[newCapacity];
transfer(newTable);
//赋值到新的table中,注意转移后会重新hash,所以位置可能会跟之前不同,目的是均匀分不到新的table中。
table = newTable;
threshold = (
int
)(newCapacity * loadFactor);
}
2)get方法
Java代码
public
V get(Object key) {
if
(key ==
null
)
return
getForNullKey();
int
hash = hash(key.hashCode());
for
(Entry<K,V> e = table[indexFor(hash, table.length)];
//找到数组的下表,进行遍历
e !=
null
;
e = e.next) {
Object k;
if
(e.hash == hash && ((k = e.key) == key || key.equals(k)))
return
e.value;
//找到则返回
}
return
null
;
//否则,返回null
}
3)remove方法
Java代码
public
V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return
(e ==
null
?
null
: e.value);
}
final
Entry<K,V> removeEntryForKey(Object key) {
int
hash = (key ==
null
) ?
0
: hash(key.hashCode());
int
i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while
(e !=
null
) {
//Entry链未遍历完则一直遍历
Entry<K,V> next = e.next;
Object k;
if
(e.hash == hash &&
((k = e.key) == key || (key !=
null
&& key.equals(k)))) {
modCount++;
size--;
if
(prev == e)
//如果是第一个,则将table[i]执行e.next
table[i] = next;
else
//否则将前一个的next指向e.next
prev.next = next;
e.recordRemoval(
this
);
return
e;
}
prev = e;
//未找到则继续往后遍历
e = next;
}
return
e;
}
4)HashMap的遍历方法
Java代码
Map map =
new
HashMap();
map.put(
"key1"
,
"value1"
);
map.put(
"key2"
,
"value2"
);
map.put(
"key3"
,
"value3"
);
for
(Iterator it = map.entrySet().iterator();it.hasNext();){
Map.Entry e = (Map.Entry)it.next();
System.out.println(e.getKey() +
"="
+ e.getValue());
}
System.out.println(
"-----------------------------------------"
);
for
(Iterator it = map.keySet().iterator();it.hasNext();){
Object key = it.next();
Object value = map.get(key);
System.out.println(key+
"="
+value);
}
System.out.println(
"-----------------------------------------"
);
System.out.println(map.values());
输出为:
key3=value3
key2=value2
key1=value1
-----------------------------------------
key3=value3
key2=value2
key1=value1
-----------------------------------------
[value3, value2, value1]
四、性能相关
1)hash
如果总计算到相同的数组下标,则得进行Entry的遍历来取值和存放值,必然会影响性能。
所以HashMap提供了hash方法,来解决key的hashCode方法质量不高问题。
Java代码
public
V put(K key, V value) {
...
int
hash = hash(key.hashCode());
...
}
static
int
hash(
int
h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>>
20
) ^ (h >>>
12
);
return
h ^ (h >>>
7
) ^ (h >>>
4
);
}
2)初始容量和负载因子
因为put的时候可能需要做扩容,扩容会导致性能损耗,所以如果可以预知Map大小的话,可以设置合理的初始大小和负载因子来避免HashMap的频繁扩容导致的性能消耗。
Java代码
void
addEntry(
int
hash, K key, V value,
int
bucketIndex) {
ntry<K,V> e = table[bucketIndex];
table[bucketIndex] =
new
Entry<K,V>(hash, key, value, e);
if
(size++ >= threshold)
resize(
2
* table.length);
}
五、同Hashtable的区别
1)HashMap允许key和value都可以为null,Hashtable都不可以为空。
HashMap的put方法,代码片段:
Java代码
if
(key ==
null
)
return
putForNullKey(value);
Hashtable的put方法,代码片段:
Java代码
if
(value ==
null
) {
throw
new
NullPointerException();
}
Entry tab[] = table;
int
hash = key.hashCode();
2)HashMap是非线程安全的,Hashtable是线程安全的。
Hashtable的put和get方法均为synchronized的。
六、ConcurrentHashMap
ConcurrentHashMap是Doug Lea写的线程安全的HashMap实现,将HashMap默认划分为了16个Segment,减少了锁的争用,另外通过写时加锁读时不加锁减少了锁的持有时间,优雅的解决了高并发下锁的高竞争问题。感兴趣的可参见笔者的另一篇博客
http://frank1234.iteye.com/blog/2162490
HashMap底层实现(源码分析)
标签:
原文地址:http://my.oschina.net/u/2400412/blog/480595
踩
(
0
)
赞
(
1
)
举报
评论
一句话评论(
0
)
登录后才能评论!
分享档案
更多>
2021年07月29日 (22)
2021年07月28日 (40)
2021年07月27日 (32)
2021年07月26日 (79)
2021年07月23日 (29)
2021年07月22日 (30)
2021年07月21日 (42)
2021年07月20日 (16)
2021年07月19日 (90)
2021年07月16日 (35)
周排行
更多
分布式事务
2021-07-29
OpenStack云平台命令行登录账户
2021-07-29
getLastRowNum()与getLastCellNum()/getPhysicalNumberOfRows()与getPhysicalNumberOfCells()
2021-07-29
【K8s概念】CSI 卷克隆
2021-07-29
vue3.0使用ant-design-vue进行按需加载原来这么简单
2021-07-29
stack栈
2021-07-29
抽奖动画 - 大转盘抽奖
2021-07-29
PPT写作技巧
2021-07-29
003-核心技术-IO模型-NIO-基于NIO群聊示例
2021-07-29
Bootstrap组件2
2021-07-29
友情链接
兰亭集智
国之画
百度统计
站长统计
阿里云
chrome插件
新版天听网
关于我们
-
联系我们
-
留言反馈
© 2014
mamicode.com
版权所有 联系我们:gaon5@hotmail.com
迷上了代码!