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

java源码分析02-HashMap

2015-11-08 19:12 573 查看
今天很开心,希望她能是我心中所期待的那位。

总结前段时间的面试,其中有一个问题特别频繁,那就是HashMap与Hashtable的区别。一般人看到这个问题就会想到java面试宝典上的答案,例如是否线程安全,key和value能否为空,contains方法存在歧义,默认容量以及扩容方式不一样等。

其实,这里考察的是我们对HashMap这个集合的理解。

首先,HashMap从使用的角度来看,就是存放键值对的并提供快速查询机制的,这种集合当然可以用作缓存。

其次,从增删改查的角度,使用HashMap的几个方法,put(key,value),remove(key),containsKey(key)以及集合的迭代输出;

这些方法的使用让我对它的内部有些好奇,首先是如果添加key相同的对象,最后输出的是后来添加的value,刚开始我很疑惑,虽然结果已经告诉我,事实就是如此,但是我还是想去了解她内心到底是怎么想的。

于是,我打开了HashMap的源码,发现他的内部既然也是数组形式存储的,这让我对她的快速查询有了一丝质疑,难道她也是顺序存储的吗?

继续看下去,发现

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
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++;
addEntry(hash, key, value, i);
return null;
}


上述代码告诉我:第一,key为null,是单独存储的;第二,我们需要计算key的哈希值,继而确定该对象在数组

中的下标值;第三,可能会出现key的哈希值相同的情况,也就是出现冲突,每次都是先保存之前的value,然后存储现有的value,但是此处不能看出,HashMap对于冲突对象是怎么处理的;第四,对于没有发生冲突的,通过addEntry方法,将对象加入到数组中。

那么,它是怎么实现快速查询的呢?

public V get(Object key) {
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;
}

int hash = (key == null) ? 0 : hash(key);
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 != null && key.equals(k))))
return e;
}
return null;
}


上述代码告诉我们,第一,key为空会单独查询,那么为什么呢?

private V getForNullKey() {
if (size == 0) {
return null;
}
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
上述代码告诉我们,原来key为空是存在下标为0的位置,那么为什么还需要一个for循环以及类似链表遍历的结构来查询呢?

我猜测可能是防止使用反射机制给Entry数组设值得情况,而且我们获取的始终是第一个key为null的value。

第二,当我们根据key获取value的时候,也是先计算对象在数组中的下标值,然后找到相应的对象,如果key相同或相等,就可以取出。

我在想,她会不会是那种适合过日子的女生呢?

她的初试容量是多大,如果生气,每次能扩大多少容量?

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;


可能存在生气的地方是如果我又多了一个女性朋友。

void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}

createEntry(hash, key, value, bucketIndex);
}


从上面可以看到,她心里有一个承受极限threshold,如果超过了,不知道会发生什么。

resize方法告诉了我们该怎么办,不可能说直接放弃,那么之前的努力就白费了。

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, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}


这里告诉我们,她的心里承受能力还是分级别的,比如和女生说话,和女生走在一起,和女生吃饭,和女生逛街,和女生看电影等等

但是,她也有一个极限,就是。。。。你懂的

那时候她就会换男朋友了。。。

void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
关于扩容是否需要重新计算哈希值,并储存
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: