HashMap源码分析

[b]一、数据结构[/b]
Map将实际数据存储在Entry类的数组中。
代码片段:
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");

[img]http://dl2.iteye.com/upload/attachment/0110/2766/49500301-f10f-3478-9869-eccbf7acbfe9.png[/img]
执行put方法时根据key的hash值来计算放到table数组的下标,如果hash到相同的下标,则新put进去的元素放到Entry链的头部,如上图所示。put方法的源码后面详细解释。

[b]二、属性和构造方法[/b]
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

构造方法一、
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}

使用了默认的容量和默认的负载因子。
构造方法二、
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

使用了用户设置的初始容量和默认的负载因子。
构造方法三、
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。是这段代码起的作用:
while (capacity < initialCapacity)
capacity <<= 1;

构造方法四、
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进行初始化。

[b]三、关键方法[/b]
1)put
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方法
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方法
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的遍历方法
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]

[b]四、性能相关[/b]
1)hash
如果总计算到相同的数组下标,则得进行Entry的遍历来取值和存放值,必然会影响性能。
所以HashMap提供了hash方法,来解决key的hashCode方法质量不高问题。
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的频繁扩容导致的性能消耗。
 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);
if (size++ >= threshold)
resize(2 * table.length);
}

[b]五、同Hashtable的区别[/b]
1)HashMap允许key和value都可以为null,Hashtable都不可以为空。
HashMap的put方法,代码片段:

if (key == null)
return putForNullKey(value);

Hashtable的put方法,代码片段:
if (value == null) {
throw new NullPointerException();
}
Entry tab[] = table;
int hash = key.hashCode();


2)HashMap是非线程安全的,Hashtable是线程安全的。
Hashtable的put和get方法均为synchronized的。
[b]六、ConcurrentHashMap[/b]
ConcurrentHashMap是Doug Lea写的线程安全的HashMap实现,将HashMap默认划分为了16个Segment,减少了锁的争用,另外通过写时加锁读时不加锁减少了锁的持有时间,优雅的解决了高并发下锁的高竞争问题。感兴趣的可参见笔者的另一篇博客[url]http://frank1234.iteye.com/blog/2162490[/url]
资源下载链接为: https://pan.quark.cn/s/3d8e22c21839 随着 Web UI 框架(如 EasyUI、JqueryUI、Ext、DWZ 等)的不断发展与成熟,系统界面的统一化设计逐渐成为可能,同时代码生成器也能够生成符合统一规范的界面。在这种背景下,“代码生成 + 手工合并”的半智能开发模式正逐渐成为新的开发趋势。通过代码生成器,单表数据模型以及一对多数据模型的增删改查功能可以被直接生成并投入使用,这能够有效节省大约 80% 的开发工作量,从而显著提升开发效率。 JEECG(J2EE Code Generation)是一款基于代码生成器的智能开发平台。它引领了一种全新的开发模式,即从在线编码(Online Coding)到代码生成器生成代码,再到手工合并(Merge)的智能开发流程。该平台能够帮助开发者解决 Java 项目中大约 90% 的重复性工作,让开发者可以将更多的精力集中在业务逻辑的实现上。它不仅能够快速提高开发效率,帮助公司节省大量的人力成本,同时也保持了开发的灵活性。 JEECG 的核心宗旨是:对于简单的功能,可以通过在线编码配置来实现;对于复杂的功能,则利用代码生成器生成代码后,再进行手工合并;对于复杂的流程业务,采用表单自定义的方式进行处理,而业务流程则通过工作流来实现,并且可以扩展出任务接口,供开发者编写具体的业务逻辑。通过这种方式,JEECG 实现了流程任务节点和任务接口的灵活配置,既保证了开发的高效性,又兼顾了项目的灵活性和可扩展性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值