Java hashmap

Java hashmap

本文基于Java 1.7 进行分析,这次分析主要是作为自己的笔记留存。

hashmap的基本原理

HashMap基于哈希算法,使用put存放对象,get获取对象,在初始设置hashmap容量时,尽量设置为2的幂次方

  • get,用户传入K,然后根据hash值,计算位置,确定在桶中的位置,然后获取value,中间需要通过K的equals()方法,hash值,key是否相等,来确定是否是取到的值。
  • put,用户将K-V传递给put后,通过K对象的hashcode()方法来计算本次存储的hash值,然后通过indexFor()方法,获取需要存入hash桶的位置,hash桶是一个数组,默认初始大小是16,里面存放的是Entry

put

public V put(K key, V value) {
    //如果table为空,使用默认值设置talbe数组和门限threshold
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        //key可以为空,处理key为空的情况,从这里可以看出为什么HashMap允许key为空
        if (key == null)
            return putForNullKey(value);
        //通过key获取hash值,hash方法里面用到了key的hashcode()方法
        int hash = hash(key);
        //获取在桶中的位置
        int i = indexFor(hash, table.length);
        //遍历hash桶,判断是否已经有值,本次是重新写入,还是已经hash冲突了
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //hash值相同且对象相同时,认为是重新写入值,覆盖旧值,hash冲突时判断对象是否相等,来区分是否覆盖写入
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        //在hash桶中添加对象以及解决hash冲突
        addEntry(hash, key, value, i);
        return null;
    }

通过上文源码以及注释可以看到,在使用hashmap时,需要作为key的对象,必须实现了hashcode和equals两个方法,保证key的唯一性,因为在hashmap中,使用hashcode来确定位置,使用equals来确定是否重复,以及区分hash冲突的情况,因为hash冲突时,一般是hashcode值一样导致,但是两个对象通过equals方法,是可以确实是不等的,这也是为什么String为什么适合作为key的原因。

在put中有一个addEntry方法,这个方法用来将k-v存入hash桶,包括了hash冲突时的处理。

 void addEntry(int hash, K key, V value, int bucketIndex) {
    //如果桶超过限制,一般为容量*0.75,就resize桶大小,扩展为2 * 当前大小
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
    //创建Entry对象,并写入桶中
        createEntry(hash, key, value, bucketIndex);
    }

    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }
      Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

通过上文代码可以看到,Entry是实际存储K-V的对象,同时也是用来解决hash冲突的单链表,当我们遇到hash冲突时,例如A,B,A已经存在桶中13这个位置,A,B hash冲突,这是,就是将B写入到A目前的位置,同时将A放入Entry的next的位置,链接起来。

get

public V get(Object key) {
        if (key == null)
            return getForNullKey();
        //获取Entry<K,V>对象
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }

    final Entry<K,V> getEntry(Object key) {
     //如果size为0,返回null
        if (size == 0) {
            return null;
        }
    //计算hash值
        int hash = (key == null) ? 0 : hash(key);
        //在桶中指定位置,遍历Entry链表,获取需要的值
        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;
        }
        //未找到 返回null
        return null;
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值