java源码学习之HashMap(一)

今天继续java源码的学习。今天突然想起自己曾经的一些往事,从高一到现在差不多六年了,记得第一次知道软件的时候是知道山寨机上的mrp软件是可以自己从网上下载了列表插件放到内存卡然后从网上下载mrp软件就可以了。后来买了java手机中兴u236玩java游戏发现手机内存不足然后就去网上论坛看各种修改软件的方法让自己的手机可以运行。也在第一次知道了游戏原来是编程语言java写的。然后就整天泡当时的手机网,历趣网,泡椒,当乐。如今只剩下当乐了了。自己也从曾经的一个高中偷偷玩手机学j2me的高中生到现在进入大四实习了。从曾经的天天逛当乐抢游戏礼包的学生变成今日想去当乐网应聘java实习生的人了。巴拉巴拉完了。
    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 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;
    }

hashmap我们一般使用其无参数构造方法建立对象。然后会建立一个长度为16的数组table,table中的元素是Entry,Entry中保存了元素的key,value,hash以及指向下一个元素的引用的next,hashmap底层数据结构是一个数组加链表的形式。然后是上面代码,进行的插入元素的操作。table不为空时,根据传入的键值对,如果键为空进行插入空键值插入操作,在大多数不为空的情况下然后根据hash方法计算出hash值,然后根据hash值通过数组下标计算出这个Entry应该位于哪一个数组下标的位置,再遍历此位置上的链表上的Entry元素的hash值或者key值是否和新传入的Entry的相应值是否相同,如果有相同的话,则将Entry中的Value更新为新的value并返回老的value。如果没有相同的,则插入新的Entry。下面来看putForNullKey方法

 private V putForNullKey(V value) {
        for (Entry 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++;
        addEntry(0, null, value, 0);
        return null;
    }
当向map中插入key为空的键值对时,获取hashmap数组第一个Entry,然后遍历这个链表直到出现第一个key为空的Entry出现或者遍历到最后一个元素。如果有key为空的元素,就将此元素的value设置为新插入的键值对的value,如果没有则在table[0]的位置放置一个新的Entry。modCount我理解为记录数据结构被更改次数的变量。

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);
    }
这是前面两个方法调用的添加一个Entry的方法。threshold的值是初始容量和装载因子的乘积,size是Entry的数量。如果size大于这个值并且table上没有空的位置进行扩容。这里只是判断是否应该进行扩容,如果不需要则调用createEntry方法,如果需要则进行扩容病重新计算新添加的Entry在table中的位置。
void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

将table保存的原来的链表的头元素存储到一个新的Entry中,再将新插入的Entry存储到table[0],并将next指向刚才的新Entry。
public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry entry = getEntry(key);

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

private V getForNullKey() {
        if (size == 0) {
            return null;
        }
        for (Entry e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }
 final Entry getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        for (Entry 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;
    }
第一段代码是hashmap中根据一个key获取value的方法。判断key值,为空调用第二段代码getForNullKey,不为空调用getEntry方法。key为空时,找到数组下标为1处的链表尾部,进行遍历,直到找到第一个key为空的Entry时,返回Entry的value。当key不为空,计算key的hash值,并以此找到key对应的Entry所在的链表在table中的位置然后遍历此处链表找到与此key的hash值相同key相同的Entry并返回。Entry不为空则返回此Entry的value
public boolean containsValue(Object value) {
        if (value == null)
            return containsNullValue();

        Entry[] tab = table;
        for (int i = 0; i < tab.length ; i++)
            for (Entry e = tab[i] ; e != null ; e = e.next)
                if (value.equals(e.value))
                    return true;
        return false;
    }
最后是containsKey和containsValue方法。containsKey是调用的getEntry方法所以效率非常高,而containsValue方法需要遍历数组和链表并且逐一做对比所以效率会远低于containsKey方法。




你可能感兴趣的:(java学习笔记,java,源码,hashmap,学习)