【数据结构】31、hashmap=》resize 扩容,不测不知道,一测吓一跳

来来来,今天就跟hashmap杠到底。。。

不要叫我杠精了,主要是还是被问到hashmap的时候,我并不能很清晰明了得告知这种数据结构到底是一个什么构造,里面细节并不了解

既然这样,我们就把他解析一波,今天这篇也算是hashmap的收官之作了,主要用来红黑树部分我之前有博文写过,但是不用深究

自己实现一个hashmap

话不多说,直接上代码,我先把这几天的成就放上来,也就是自己实现的hashmap,还原到以前的版本,我把红黑树的部分代码给删除了

package y2019.collection;

import java.util.Map;
import java.util.Objects;

/**
 * @ProjectName: cutter-point
 * @Package: y2019.collection
 * @ClassName: MyMyHashMap
 * @Author: xiaof
 * @Description: 在JDK8中,当链表长度达到8,会转化成红黑树,以提升它的查询、插入效率
 *              底层哈希桶的数据结构是数组,所以也会涉及到扩容的问题。
 *              当MyHashMap的容量达到threshold域值时,就会触发扩容。扩容前后,哈希桶的长度一定会是2的次方。
 *       这个类的目标是为了实现MyHashMap中的数组,hash扰动之后转链表的操作(后续可以考虑完善红黑树结构)
 * @Date: 2019/6/25 9:08
 * @Version: 1.0
 */
public class MyHashMap<K,V> {

    //容器最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //用来存放NODE数据的数组
    transient Node<K,V>[] table;
    /**
     * hash桶默认长度
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    //默认加载因子,加载因子是一个比例,当HashMap的数据大小>=容量*加载因子时,HashMap会将容量扩容
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    //hash桶的阈值
    int threshold;
    //装载因子用来衡量HashMap满的程度
    float loadFactor;
    transient int modCount;
    transient int size;

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                        Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

    public static int hash(Object key) {
        int h;
        //也就将key的hashCode无符号右移16位然后与hashCode异或从而得到hash值在putVal方法中(n - 1)& hash计算得到桶的索引位置
        //注意,这里h是int值,也就是32位,然后无符号又移16位,那么就是折半,折半之后和原来的数据做异或操作,正好整合了高位和低位的数据
        //混合原始哈希码的高位和低位,以此来加大低位的随机性,而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    //测试,如果我们不做高位低位的操作看看hash冲突是大还是小
    public static int hash2(Object key) {
        return (int) key;
    }

    public static int hash3(Object key) {
        int h = key.hashCode();
        //我们不做右移试试,那就自己跟自己异或。。。没意义,只能是0了
        return (key == null) ? 0 : h ^ h;
    }

    public static int hash4(Object key) {
        int h;
        //我们不做右移试试,或者右移8位试试
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 8);
    }

    public static int hash5(Object key) {
        int h;
        //我们不做右移试试,或者右移8位试试
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 4);
    }

    public static int hash6(Object key) {
        int h;
        //我们不做右移试试,或者右移8位试试
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 2);
    }

    public int quyu1(int num, int n) {
        //对num进行n取余
        return num % n;
    }

    public int quyu2(int num, int n) {
        //对num进行n取余
        return num & (n - 1);
    }

    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        //注意这一步中(n - 1) & hash 的值 等同于 hash(k)%table.length
        if ((tab = table) != null && (n = tab.length) > 0 &&
                //这里是计算相当于是取余的索引位置(n - 1) & hash 等价于hash % n
                //而且由于hashmap中的length再tableSizeFor的时候,就把长度设置为2的n次幂了,那么n-1之后的值,就是最高位全都是0,下面位数全是1
                //这个也就是取hash的低位的值
                (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                //暂时不考虑红黑树
//                if (first instanceof TreeNode)
//                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

    public V get(Object key) {
        MyHashMap.Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    /**
     *
     * @program: y2019.collection.MyHashMap
     * @description: 这个方法用于找到大于等于initialCapacity的最小的2的幂(initialCapacity如果就是2的幂,则返回的还是这个数)。
     * @auther: xiaof
     * 总结:
     * 1.说白了就是为了保证所有的位数(二进制)都是1,那么就可以保证这个数就是2的幂
     * 2.不断做无符号右移,是为了吧高位的数据拉下来做或操作,来保证对应的底位都是1
     * @date: 2019/6/25 10:25
     */
    public static final int tableSizeFor(int cap) {
        //这是为了防止,cap已经是2的幂。如果cap已经是2的幂
        int n = cap - 1;
        //第一次右移,由于n不等于0(如果为0,不管几次右移都是0,那么最后有个n+1的操作),则n的二进制表示中总会有一bit为1
        //这里无符号右移一位之后做或操作,那么会导致原来有1的地方紧接着也是1
        //比如00000011xxxxxxxx
        //还有一点无符号右移是为了避免前位补1,导致数据溢出,因为负数是以补码的形式存在的,那么就会再高位补1
        n |= n >>> 1;
        //第二次无符号右移,并做或操作
        //00000011xxxxxxxx=>0000001111xxxxxx 这个时候就是4个1
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        //由于int最大也就是2的16次幂,所以到16停止
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

    public static final int tableSizeFor2(int cap) {
        //这是为了防止,cap已经是2的幂。如果cap已经是2的幂
        int n = cap - 1;
        n |= n & 0xffff;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

    public static final int tableSizeFor3(int cap) {
        //这是为了防止,cap已经是2的幂。如果cap已经是2的幂
        int n = (cap - 1) & 0xffff;
        String hex = Integer.toBinaryString(n);
        return (cap <= 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : (int) Math.pow(2, hex.length());
    }

    Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
        return new Node<>(hash, key, value, next);
    }

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    public V put2(K key, V value) {
        return putVal2(hash(key), key, value, false, true);
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //当table为空时,这里初始化table,不是通过构造函数初始化,而是在插入时通过扩容初始化,有效防止了初始化HashMap没有数据插入造成空间浪费可能造成内存泄露的情况
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //存放新键值对
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            //旧键值对的覆盖
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
//            else if (p instanceof TreeNode)
//                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //链表存放
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        //链表尾部插入
                        p.next = newNode(hash, key, value, null);
                        //当链表的长度大于等于树化阀值,并且hash桶的长度大于等于MIN_TREEIFY_CAPACITY,链表转化为红黑树
//                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//                            treeifyBin(tab, hash);
                        break;
                    }
                    //链表中包含键值对
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //map中含有旧key,返回旧值
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
//                afterNodeAccess(e);
                return oldValue;
            }
        }
        //map调整次数加1
        ++modCount;
        //键值对的数量达到阈值需要扩容
        if (++size > threshold)
            resize();
//        afterNodeInsertion(evict);
        return null;
    }

    final V putVal2(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //当table为空时,这里初始化table,不是通过构造函数初始化,而是在插入时通过扩容初始化,有效防止了初始化HashMap没有数据插入造成空间浪费可能造成内存泄露的情况
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize2()).length;
        //存放新键值对
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            //旧键值对的覆盖
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
//            else if (p instanceof TreeNode)
//                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //链表存放
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        //链表尾部插入
                        p.next = newNode(hash, key, value, null);
                        //当链表的长度大于等于树化阀值,并且hash桶的长度大于等于MIN_TREEIFY_CAPACITY,链表转化为红黑树
//                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//                            treeifyBin(tab, hash);
                        break;
                    }
                    //链表中包含键值对
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //map中含有旧key,返回旧值
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
//                afterNodeAccess(e);
                return oldValue;
            }
        }
        //map调整次数加1
        ++modCount;
        //键值对的数量达到阈值需要扩容
        if (++size > threshold)
            resize2();
        return null;
    }

    //数组扩容
    public Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        //如果旧hash桶不为空
        if (oldCap > 0) {
            ////超过hash桶的最大长度,将阀值设为最大值
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //新的hash桶的长度2被扩容没有超过最大长度,将新容量阀值扩容为以前的2倍
            //扩大一倍之后,小于最大值,并且大于最小值
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                    oldCap >= DEFAULT_INITIAL_CAPACITY)
                //左移1位,也就是扩大2倍
                newThr = oldThr << 1;
        }
        else if (oldThr > 0) //如果旧的容量为空,判断阈值是否大于0,如果是那么就把容量设置为当前阈值
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }

        //如果阈值还是0,重新计算阈值
        if (newThr == 0) {
            //当HashMap的数据大小>=容量*加载因子时,HashMap会将容量扩容
            float ft = (float)newCap * loadFactor;
            //如果容量还没超MAXIMUM_CAPACITY的loadFactor时候,那么就返回ft,否则就是反馈int的最大值
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                    (int)ft : Integer.MAX_VALUE);
        }
        //hash桶的阈值
        threshold = newThr;
        //初始化hash桶
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;

        if (oldTab != null) {
            //遍历旧数组
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                //如果旧的hash桶不为空,需要将旧的hash表里的键值对重新映射到新的hash桶中
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    //只有一个节点,通过索引位置直接映射
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;  //取余
                        //如果是红黑树,需要进行树拆分然后映射
//                    else if (e instanceof TreeNode)
//                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        //如果是多个节点的链表,将原链表拆分为两个链表,两个链表的索引位置,一个为原索引,一个为原索引加上旧Hash桶长度的偏移量
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
//                            在遍历原hash桶时的一个链表时,因为扩容后长度为原hash表的2倍,假设把扩容后的hash表分为两半,分为低位和高位,
//                            如果能把原链表的键值对, 一半放在低位,一半放在高位,这样的索引效率是最高的
                            //这里的方式是e.hash & oldCap,
                            //经过rehash之后,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。对应的就是下方的resize的注释
                            //为什么是移动2次幂呢??注意我们计算位置的时候是hash&(length - 1) 那么如果length * 2 相当于左移了一位
                            //也就是截取的就高了一位,如果高了一位的那个二进制正好为1,那么结果也相当于加了2倍
                            //hash & (length * 2 - 1) = length & hash + (length - 1) & hash
                            if ((e.hash & oldCap) == 0) {
                                //如果这个为0,那么就放到lotail链表
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                //如果length & hash 不为0,说明扩容之后位置不一样了
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            //而这个loTail链表就放在原来的位置上
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            //因为扩容了2倍,那么新位置就可以是原来的位置,右移一倍原始容量的大小
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

    //数组扩容
    public Node<K,V>[] resize2() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        //如果旧hash桶不为空
        if (oldCap > 0) {
            ////超过hash桶的最大长度,将阀值设为最大值
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //新的hash桶的长度2被扩容没有超过最大长度,将新容量阀值扩容为以前的2倍
            //扩大一倍之后,小于最大值,并且大于最小值
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                    oldCap >= DEFAULT_INITIAL_CAPACITY)
                //左移1位,也就是扩大2倍
                newThr = oldThr << 1;
        }
        else if (oldThr > 0) //如果旧的容量为空,判断阈值是否大于0,如果是那么就把容量设置为当前阈值
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }

        //如果阈值还是0,重新计算阈值
        if (newThr == 0) {
            //当HashMap的数据大小>=容量*加载因子时,HashMap会将容量扩容
            float ft = (float)newCap * loadFactor;
            //如果容量还没超MAXIMUM_CAPACITY的loadFactor时候,那么就返回ft,否则就是反馈int的最大值
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                    (int)ft : Integer.MAX_VALUE);
        }
        //hash桶的阈值
        threshold = newThr;
        //初始化hash桶
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;

        if (oldTab != null) {
            //遍历旧数组
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                //如果旧的hash桶不为空,需要将旧的hash表里的键值对重新映射到新的hash桶中
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    //只有一个节点,通过索引位置直接映射
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;  //取余
                        //如果是红黑树,需要进行树拆分然后映射
//                    else if (e instanceof TreeNode)
//                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        //如果是多个节点的链表,将原链表拆分为两个链表,两个链表的索引位置,一个为原索引,一个为原索引加上旧Hash桶长度的偏移量
                        Node<K,V> next, pre;
                        pre = e;
                        do {
                            next = e.next;
                            //我们这里直接遍历设置进去试试
                            //对hash数据取余,当然如果还是再原来的位置,那么就不需要移动
                            if((e.hash & (oldCap - 1)) != (e.hash & (newCap - 1))) {
                                //1.先从原链表断开
                                pre.next = next;
                                //2.放到新位置上,我们可以使用头插法
                                Node<K,V> newHead, newNext;
                                newHead = newTab[e.hash & (newCap - 1)];
                                if(newHead == null) {
                                    newHead = e;
                                } else {
                                    //头插法
                                    newNext = newHead.next;
                                    newHead.next = e;
                                    e.next = newNext;
                                }
                            }
                            pre = e;
                        } while ((e = next) != null);
                    }
                }
            }
        }
        return newTab;
    }

    public Node<K, V>[] getTable() {
        return table;
    }

    public void setTable(Node<K, V>[] table) {
        this.table = table;
    }

    public int getThreshold() {
        return threshold;
    }

    public void setThreshold(int threshold) {
        this.threshold = threshold;
    }

    public float getLoadFactor() {
        return loadFactor;
    }

    public void setLoadFactor(float loadFactor) {
        this.loadFactor = loadFactor;
    }

    public int getModCount() {
        return modCount;
    }

    public void setModCount(int modCount) {
        this.modCount = modCount;
    }
}

注意resize 的扩容操作

1.啥时候扩容???

说白了就数据量超了就扩容被,那么什么时候叫超了呢???

很简单,就是hashmap的当前容量大于cap*loadFactor,cap是可以容纳的容量,loadFactor是一个百分比,就是到达多少的量了默认0.75f;

而且这个参数是可以改的

2.还有一种情况,网上说再扩容的时候,使用双链表直接连接的效率很高!!!

在遍历原hash桶时的一个链表时,因为扩容后长度为原hash表的2倍,假设把扩容后的hash表分为两半,分为低位和高位,如果能把原链表的键值对, 一半放在低位,一半放在高位,这样的索引效率是最高的这里的方式是e.hash & oldCap,经过rehash之后,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。对应的就是下方的resize的注释为什么是移动2次幂呢??注意我们计算位置的时候是hash&(length - 1) 那么如果length * 2 相当于左移了一位也就是截取的就高了一位,如果高了一位的那个二进制正好为1,那么结果也相当于加了2倍hash & (length * 2 - 1) = length & hash + (length - 1) & hash

我个人比较相信权威,但是我不是很理解,你这样双链表,你两个链表都要操作一次吧,所有的元素都要进行操作吧

那我为什么不用单链表,头插法搞呢???

我直接再原链表上断开元素连接,然后把新元素头插进入新位置会不会更快呢???

说干就干,来走一波!!!

搞,测试走起来。。。。

测试用例

@org.junit.jupiter.api.Test
    public void testResize() {

        int init = 10000;

        for(int j = 0; j < 10; ++j) {
            int size = (int) (init * Math.pow(2, j + 1));

            HashMap HashMap1 = new HashMap();
            long begin0 = System.currentTimeMillis();
            for(int i = 0; i < size; ++i) {
                HashMap1.put(i, "i" + i);
            }
            long end0 = System.currentTimeMillis();
            System.out.print("jkd1.8(原滋原味)耗时:" + (end0 - begin0) + "\t");

            MyHashMap myHashMap1 = new MyHashMap();
            long begin = System.currentTimeMillis();
            for(int i = 0; i < size; ++i) {
                myHashMap1.put(i, "i" + i);
            }
            long end = System.currentTimeMillis();
            System.out.print("jkd1.8(没有红黑树)耗时:" + (end - begin) + "\t");

            MyHashMap myHashMap2 = new MyHashMap();
            long begin2 = System.currentTimeMillis();
            for(int i = 0; i < size; ++i) {
                myHashMap2.put2(i, "i" + i);
            }
            long end2 = System.currentTimeMillis();

            System.out.println("正常取余链表头插法耗时:" + (end2 - begin2));
        }

    }

来看看结果吧。。。

这。。。

我这又懵逼了???

啥情况???

说好的大神操作呢?

我们再试2次?


 结果毫不意外的,简单的头插法的扩容效率好像比原版的效率高很多??jdk源码中的做法是不是有点过度设计了呢???

原文地址:https://www.cnblogs.com/cutter-point/p/11096247.html

时间: 2024-10-24 16:33:25

【数据结构】31、hashmap=》resize 扩容,不测不知道,一测吓一跳的相关文章

HashMap的扩容机制

复习散列数据结构之余重新研究了一下Java中的HashMap:本文主要针对:1.HashMap的初始化:2.HashMap的插入:3:HashMap的扩容这三个方面进行总结 1.HashMap的初始化 首先我们来看看代码: 1 public HashMap(int initialCapacity, float loadFactor) { 2 if (initialCapacity < 0) 3 throw new IllegalArgumentException("Illegal init

Java HashMap的扩容

最近博主参加面试,发现自己对于Java的HashMap的扩容过程理解不足,故最近在此进行总结. 首先说明博主德Java为1.8版本 HashMap中的变量 首先要了解HashMap的扩容过程,我们就得了解一些HashMap中的变量: Node<K,V>:链表节点,包含了key.value.hash.next指针四个元素 table:Node<K,V>类型的数组,里面的元素是链表,用于存放HashMap元素的实体 size:记录了放入HashMap的元素个数 loadFactor:负

jdk1.8 HashMap的扩容resize()方法详解

/** * Initializes or doubles table size. If null, allocates in * accord with initial capacity target held in field threshold. * Otherwise, because we are using power-of-two expansion, the * elements from each bin must either stay at same index, or mo

转发 java数据结构之hashMap详解

概要 这一章,我们对HashMap进行学习.我们先对HashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashMap.内容包括:第1部分 HashMap介绍第2部分 HashMap数据结构第3部分 HashMap源码解析(基于JDK1.6.0_45)第3.1部分 HashMap的“拉链法”相关内容第3.2部分 HashMap的构造函数第3.3部分 HashMap的主要对外接口第3.4部分 HashMap实现的Cloneable接口第3.5部分 HashMap实现的Seria

【数据结构】HashMap原理及实现学习总结

HashMap是Java中最常用的集合类框架之一,是Java语言中非常典型的数据结构.本篇主要是从HashMap的工作原理,数据结构分析,HashMap存储和读取几个方面对其进行学习总结.关于HashMap的完整源码分析请查看下一篇. 一. HashMap的工作原理 HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象.当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象.当获取

HashMap的扩容机制, ConcurrentHashMap和Hashtable主要区别

源代码查看,有三个常量, static final int DEFAULT_INITIAL_CAPACITY = 16; static final int MAXIMUM_CAPACITY = 1 << 30; static final float DEFAULT_LOAD_FACTOR = 0.75f; 三个常量中可以看出,默认的容器大小是16,最大长度是2的30次方,load factor默认是0.75,扩充的临界值是16*0.75=12 当我们往HashMap中put元素的时候,先根据k

jdk1.8 HashMap底层数据结构:深入解析为什么jdk1.8 HashMap的容量一定要是2的n次幂

前言 1.本文根据jdk1.8源码来分析HashMap的容量取值问题: 2.本文有做 jdk1.8 HashMap.resize()扩容方法的源码解析:见下文“一.3.扩容:同样需要保证扩容后的容量是2的n次幂”: 3.目录: 一.jdk1.8中,对“HashMap的容量一定是2的n次幂”做了严格控制 1.默认初始容量 2.使用HashMap的有参构造函数来自定义容量的大小(保证容量是2的n次幂) 3.扩容:同样需要保证扩容后的容量是2的n次幂( jdk1.8 HashMap.resize()扩

Java中的数据结构-HashMap

Java数据结构-HashMap 目录 Java数据结构-HashMap 1. HashMap 1.1 HashMap介绍 1.1.1 HashMap介绍 1.1.2 HashMap继承图 1.2 HashMap 组成结构 1.2.1 Hashmap底层数据结构 2.HashMap源码解析 2.1 HashMap属性源码解析 2.1.1 HashMap中的静态常量 2.1.2 HashMap中的属性 2.1.3 HashMap中的存储结点 2.1.4 Hash表 2.2 HashMap数据的改变

HashMap数据结构

2.1 HashMap 2.1.1 HashMap介绍 先看看HashMap类头部的源码: public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了非同步和允许使用 null 之外,HashMap 类与 Hash