您的位置:首页 > 编程语言 > Java开发

HashMap源码分析

2015-11-04 15:16 393 查看
  在上一篇博客中,大致介绍了散列以及Java Map的结构,这一篇主要分析HashMap的源代码,主要理解HashMap是如何保存数据、取数据、如何扩容、遍历的效率的对比。分析的源码版本为:java version “1.7.0_71”。

  (1)构造函数

  我个人最常用的构造函数是参数为空的,这个构造函数会调用另外一个带参数的构造函数, 并传入默认容量16和默认的loadFactor 0.75。这个构造函数比较简单,只进行校验参数和赋值的操作。

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);

this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}


  (2)接下来是重要的put方法,put方法用于将键值对保存到Map中,让我们来具体分析一下。

  

public V put(K key, V value) {
//若table为空的table,首先进行一些初始化操作
//table初始长度为16
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
//若key为null,则将value放到table第0个位置
//若有值,将覆盖
return putForNullKey(value);
//对key进行一次hash,目的在于使得获得的hash值中的0、1均匀
int hash = hash(key);
//获得该key在table中的位置
int i = indexFor(hash, table.length);
//获得i这个位置的Entry对象,判断是否为空,若不为空则遍历i这个位置的链表,如果有相同的key,将旧的value覆盖
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;
}
}

modCount++;
//往一个位置i添加Entry
addEntry(hash, key, value, i);
return null;
}


  然后看看addEntry的实现。

  

void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
//两倍扩充容量
resize(2 * table.length);
//重新计算hash值
hash = (null != key) ? hash(key) : 0;
//计算在新table中的位置
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}

//扩容
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的过程其实也挺简单,只是根据initHashSeedAsNeeded(newCapacity)的结果决定是否需要rehash
//如果需要rehash就重新hash一次,找到新位置,否则就根据原来的hash值找到在新表中的位置
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}


  至此put方法已经分析完毕,总结一下:(1)如果key为null,将Entry放到第一个位置,如果存在则覆盖。(2)对key进行hash,找到位置,如果存在相同的key则覆盖。(3)判断一下是否需要扩容,如需要则将size变成2倍,根据hashSeed计算是否需要rehash,将已经存在的Entry赋值到新table。(4)将新的Entry放到合适的位置。

  接下来分析一下get方法。

public V get(Object key) {
//key为null,去第0个位置找
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);

return null == entry ? null : entry.getValue();
}

final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
//计算hash值
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
//找到相应位置并开始遍历,找到key相等的Entry然后返回value
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}


  get方法总结如下:(1)判断key是否为null,如果是去第0个位置取值。(2)计算hash值,找到在table中的位置,遍历那个位置上的Entry链表,找到key相等的并返回value。

  HashMap有两种典型的遍历方式。

Map<String, String> map = new HashMap<String, String>();
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key : " + entry.getKey() + ", value" + entry.getKey());
}
Set<String> set = map.keySet();
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String key = it.next();
System.out.println("key : " + key + ", value" + map.get(key));
}


 这两种遍历方式调用的keySet和entrySet方法,本质上没有太大的区别,都调用了HashIterator的nextEntry方法,keySet调用的是nextEntry.getKey方法。但是从最终的遍历来看,entrySet的遍历效率应该是比keySet稍高的,推荐用这个方式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 源码 hashmap