HashMap源码分析

  在上一篇博客中,大致介绍了散列以及Java Map的结构,这一篇主要分析HashMap的源代码,主要理解HashMap是如何保存数据、取数据、如何扩容、遍历的效率的对比。分析的源码版本为:java version “1.7.0_71”。
  (1)构造函数
  我个人最常用的构造函数是参数为空的,这个构造函数会调用另外一个带参数的构造函数, 并传入默认容量16和默认的loadFactor 0.75。这个构造函数比较简单,只进行校验参数和赋值的操作。

public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }

  (2)接下来是重要的put方法,put方法用于将键值对保存到Map中,让我们来具体分析一下。
  

    public V put(K key, V value) {
        //若table为空的table,首先进行一些初始化操作
        //table初始长度为16
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            //若key为null,则将value放到table第0个位置
            //若有值,将覆盖
            return putForNullKey(value);
        //对key进行一次hash,目的在于使得获得的hash值中的0、1均匀
        int hash = hash(key);
        //获得该key在table中的位置
        int i = indexFor(hash, table.length);
        //获得i这个位置的Entry对象,判断是否为空,若不为空则遍历i这个位置的链表,如果有相同的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;
            }
        }

        modCount++;
        //往一个位置i添加Entry
        addEntry(hash, key, value, i);
        return null;
    }

  然后看看addEntry的实现。
  

void addEntry(int hash, K key, V value, int bucketIndex) {
       if ((size >= threshold) && (null != table[bucketIndex])) {
        //两倍扩充容量
           resize(2 * table.length);
           //重新计算hash值
           hash = (null != key) ? hash(key) : 0;
           //计算在新table中的位置
           bucketIndex = indexFor(hash, table.length);
       }
       createEntry(hash, key, value, bucketIndex);
   }

//扩容
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的过程其实也挺简单,只是根据initHashSeedAsNeeded(newCapacity)的结果决定是否需要rehash
      //如果需要rehash就重新hash一次,找到新位置,否则就根据原来的hash值找到在新表中的位置
      transfer(newTable, initHashSeedAsNeeded(newCapacity));
      table = newTable;
      threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
  }

  至此put方法已经分析完毕,总结一下:(1)如果key为null,将Entry放到第一个位置,如果存在则覆盖。(2)对key进行hash,找到位置,如果存在相同的key则覆盖。(3)判断一下是否需要扩容,如需要则将size变成2倍,根据hashSeed计算是否需要rehash,将已经存在的Entry赋值到新table。(4)将新的Entry放到合适的位置。
  接下来分析一下get方法。

    public V get(Object key) {
        //key为null,去第0个位置找
        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;
        }
        //计算hash值
        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            //找到相应位置并开始遍历,找到key相等的Entry然后返回value
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

  get方法总结如下:(1)判断key是否为null,如果是去第0个位置取值。(2)计算hash值,找到在table中的位置,遍历那个位置上的Entry链表,找到key相等的并返回value。
  HashMap有两种典型的遍历方式。

Map<String, String> map = new HashMap<String, String>();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println("key : " + entry.getKey() + ", value" + entry.getKey());
        }
        Set<String> set = map.keySet();
        Iterator<String> it = set.iterator();
        while (it.hasNext()) {
            String key = it.next();
            System.out.println("key : " + key + ", value" + map.get(key));
        }

 这两种遍历方式调用的keySet和entrySet方法,本质上没有太大的区别,都调用了HashIterator的nextEntry方法,keySet调用的是nextEntry.getKey方法。但是从最终的遍历来看,entrySet的遍历效率应该是比keySet稍高的,推荐用这个方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值