您的位置:首页 > 理论基础 > 数据结构算法

HashMap的工作原理

2014-03-08 14:48 323 查看
1.    HashMap概述:

   HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。不保证映射的顺序,特别是它不保证该顺序恒久不变。

 
2.    HashMap的数据结构:

   HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

   从上图中可以看出,HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。

   数组中存放的键值对:

 static class
Entry<K,V> implements Map.Entry<K,V> {

        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;

        }

    }

               Entry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的
4000
引用,这就构成了链表

3. HashMap的存取实现:
       1.存储

public V put(K key, V value) {
        //键值为null,采取特别处理

        if (key == null)

            return putForNullKey(value);
        //计算键值的hash值

        int hash = hash(key.hashCode());

//计算  h & (length-1),获得在数组中的序号

        int i = indexFor(hash, table.length);

//如果该位置上已经有键值对了,遍历这个位置上的链表,如果找到相同的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;

            }

        }

        //该位置上没有存储键值对或者不存在相同的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];

//创建键值对,存储在该位置上,其next指向的原来的键值对

        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);

//实际容量超过预定负载后,把长度扩展一倍
//threshold = (int)(capacity * loadFactor) 
//capacity必须为2的幂
//loadFactor默认为0.75,可以在构造函数中制定

        if (size++ >= threshold)
            resize(2 * table.length);
    }

private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;

//null键总是放在数组第一位置上的链表中

        addEntry(0, null, value, 0);
        return null;
    }  

   2.获取      
    

public V get(Object key) {

//键值为null,采取特别处理

        if (key == null)

            return getForNullKey();

//计算键值的hash值

        int hash = hash(key.hashCode());
        //通过hash,计算出其存储位置的索引,在该位置上遍历链表,取得键值对,返回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;

    }
 //在数组的第一个位置上遍历链表,查找key为null的键值对

 private V getForNullKey() {

        for (Entry<K,V> e = table[0]; e != null; e = e.next) {

            if (e.key == null)

                return e.value;

        }

        return null;

    }

4.确定数组index:hashcode % table.length取模

HashMap存取时,都需要计算当前key应该对应Entry[]数组哪个元素,即计算数组下标
   /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }
 
按位取并,作用上相当于取模mod或者取余%。
这意味着数组下标相同,并不表示hashCode相同。

                                                                                                         
                                     
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Java hashmap 数据结构