无论是什么事或物,如果只看到表面的东西而不知根本的原理,那么换过角度再去看的话可能就有些茫然。正所谓只知其然不知其所以然。
搞开发也是一样,有的类常用,但光从使用上也很难真正了解它底层的实现。在查看API时,偶然间我想到了一个HashMap.keySet()的问题,通过实验得到与推理不一样的结果,心里就有点纠结。好在API有源码可以查看。其实很早就知道这东西,但是一直都没有查看的欲望。而今天时间、欲望都具备,正好是饱餐一顿的时候了。
首先关把源码关联上,这个就不多说了。百度一下你就知道!嘿嘿。
在进入主题之前先来理理类的结构,这样好有一个清晰的思路。在Util包中,Map是作为一个单独的分支存在。HashMap继承了AbstractMap类,实现了Map接口。
在测试代码中选中keySet(),然后按F3就直接进入API源码中。这时候可以看到keySet()方法是这样定义的:
public Set<K> keySet() {
Set<K> ks = keySet;//①
return (ks != null ? ks : (keySet = new KeySet()));②
}
① 处的keySet是AbstractMap类的成员变量。AbstractMap类中有一个返回keySet的方法keySet();其实现如下:
public Set<K> keySet() {
if (keySet == null) {
keySet = new AbstractSet<K>() {
public Iterator<K> iterator() {
return new Iterator<K>() {
private Iterator<Entry<K,V>> i = entrySet().iterator();
public boolean hasNext() {
return i.hasNext();
}
public K next() {
return i.next().getKey();
}
public void remove() {
i.remove();
}
};
}
public int size() {
return AbstractMap.this.size();
}
public boolean contains(Object k) {
return AbstractMap.this.containsKey(k);
}
};
}
return keySet;
}
这里要注意的是一个实现了HashMap.Entry接口的内部类Entry<K,V>,在后面put()和get()方法中会重点讲解
②处的KeySet()是HashMap类里继承了AbstractSet类的一个内部类,
public Iterator<K> iterator() {
return newKeyIterator();
}
上面的代码是这个内部类里面的一个方法,也是HashMap.keySet()的主要实现。可以看出这个方法是迭代出Key值。下面我们可以追溯到它的最终实现:
Iterator<K> newKeyIterator() {
return new KeyIterator();
}
private final class KeyIterator extends HashIterator<K> {
public K next() {
return nextEntry().getKey();
}
}
private abstract class HashIterator<E> implements Iterator<E> {
………………
final Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
//判断当前hash桶里的当前变量是否为数据链的最后一个,
//如果是最后一个,并且hash桶不是最后一个时,把下一个桶的元//素赋给next变量,并把指向hash桶的变量修改为指向下一个桶
if ((next = e.next) == null) {
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
current = e;
return e;
}
……
}
从nextEntry()方法可以看出,在hashMap里虽有相同的散列码,但Key值不相等的元素都会一个不漏的被搜索出来. 出来不难看出HashMap是表加链表的数据结构。我们可以用下图来表示它的存贮方式:
|
有了上面的分析和存贮结构,下面我们来看看HashMap.put()方法的实现,这样应该会很容易就理解。我们把put方法的实现以及相关的代码拷贝如下:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
//桶的位置
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//判断相同散列码的元素是否有相同的Key值,如果Key相同的话
//用新值把原来的值覆盖,并返回旧值。
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value; //为返回值赋值
e.value = value; //覆盖旧值
e.recordAccess(this); //空方法(未实现)
return oldValue;
}
}
modCount++;
//添加新的Entry元素
addEntry(hash, key, value, i);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
//在链的头加入新元素。如果hash桶中原来为空的话,next值为NULL
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
//如果超过table容量时,增加一原来容量的一倍
if (size++ >= threshold)
resize(2 * table.length);
}
//链的数据结构,此结构解决具相同散列码但Key值不相同的元素存贮问题
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
…………
}
Get()方法相对于put()方法来说要简单得多,它实现代码如下。
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
//在indexFor(hash,table.length)位置的Hash桶里循环搜索
//e.key = Key 的value
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;
}
在HashMap中有几个成员变量要注意一下:
//默认长度
static final int DEFAULT_INITIAL_CAPACITY = 16;
//加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//实际长度
int threshold;
//修改次数
transient volatile int modCount;
由threshold = DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR
可得出 threshold = 12。在构建HashMap时,默认的实际容量只有12,如果随着容量的不停增加,超过容量时,HashMap会不断的扩大容量,扩容的结果就是不断的把以前的数据拷到新的容器中,这样反复的扩容会带来性能上的损耗,所以在考虑到性能问题时,应该根据需要建立适当容量的HashMap,减少扩容的次数减少不必要的时间开销。
此内容为自己理解所写,如有错误之处请大家多多指教。