hashMap之旅

    无论是什么事或物,如果只看到表面的东西而不知根本的原理,那么换过角度再去看的话可能就有些茫然。正所谓只知其然不知其所以然。

搞开发也是一样,有的类常用,但光从使用上也很难真正了解它底层的实现。在查看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()));

}

       处的keySetAbstractMap类的成员变量。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,减少扩容的次数减少不必要的时间开销。

此内容为自己理解所写,如有错误之处请大家多多指教。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值