java源码分析02-HashMap

来源:互联网 发布:宁夏善知律师事务所 编辑:程序博客网 时间:2024/05/05 10:43

今天很开心,希望她能是我心中所期待的那位。

总结前段时间的面试,其中有一个问题特别频繁,那就是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;            }        }    }
关于扩容是否需要重新计算哈希值,并储存

0 0
原创粉丝点击