集合——HashSet

    • HashSet特征
    1. 无序——添加和取出元素的顺序不一致;没有索引
    2. 元素不可以重复,所以最多包含一个null
    3. 取出的顺序虽然不是添加的顺序,但是固定
    • 底层采用HashMap实现,所以主要介绍HashMap底层代码
    • HashMap底层实现:数组+链表/红黑树
    集合——HashSet

     

    HashMap大致数据结构

    • 看代码⬇️
    public class test {
        public static void main(String[] args) {
            //创建一个HashSet对象
            HashSet set = new HashSet();
            for (int i = 1 ;i <= 20; i++){
                //添加元素
                set.add(i);
            }
            System.out.println(set);
        }
    }
    1. HashSet set =newHashSet();
    public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable
    {
        public HashSet() {
            //创建一个HashMap对象
            map = new HashMap<>();
        }
    }

    看到没有!HashMap,接着往下看

    2. map = new HashMap<>();

    public class HashMap<K,V> extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable, Serializable 
    {
        //指定数组的初始大小为16. 1<<4=1*2*2*2*2
        static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
        //构造函数
        public HashMap() {
            /*给loadFactor赋值 0.75
              用来计算数组的临界点下标
            */
            this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
        }
    }

    3. set.add(i);

    public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable
    {
        //添加一个元素 返回boolean类型表示成功或失败
        public boolean add(E e) {
            //PRESENT——表示map<K,V>中的value,这里为一个Object对象,K=e——在键中存储要添加的元素
            return map.put(e, PRESENT)==null;
        }
    }

    4. map.put(e, PRESENT)

    public class HashMap<K,V> extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable, Serializable 
    {
        public V put(K key, V value) {
            //hash(key)用来计算hash值,最终得到将要存储元素的下标
            //key,value即Map的键值对
            return putVal(hash(key), key, value, false, true);
        }
    }

    5. putVal(hash(key), key, value,false,true);

    public class HashMap<K,V> extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable, Serializable 
    {
        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 = Map结构中的数组初始为空
            if ((tab = table) == null || (n = tab.length) == 0)
                //一开始初始化准备添加第一个元素,上面条件成立,先看下resize代码 -> 6.1
                n = (tab = resize()).length;
            //如果通过hash算法得到的下标没有存储任何元素,则将元素通过Node存放在当前下标
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;
                /*判断如果当前要存储hash值与计算出的hash值相等并且将要存储的元素的地址或内容与
                  原下标的地址或内容相等则执行
                */
                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 {
                    //hash值相同,其他上述条件不成立时.循环数组当前下标的链表中的元素
                    for (int binCount = 0; ; ++binCount) {
                        //之所以要判断链表下一节点是否指向空,是因为要将新的节点添加到链表尾部
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            /*判断如果当前下标的链表中节点的数量>=7(从0开始数),则考虑将链表
                              转为红黑树
                            */
                            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;
                    }
                }
                //如果e不是null,返回的也不是null 表示添加失败
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            //如果数组大小 > 数组大小临界点,则扩容
            if (++size > threshold)
                resize();
            //没有意义的方法
            afterNodeInsertion(evict);
            //返回null 表示添加成功
            return null;
        }
    

    }

    6.1. resize()

    public class HashMap<K,V> extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable, Serializable 
    {
        final Node<K,V>[] resize() {
            Node<K,V>[] oldTab = table;
            //oldCap=0,因为数组现在为null
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            //oldThr=0,默认为0 —数组长度临界值old
            int oldThr = threshold;
            int newCap, newThr = 0;
            //不成立,跳过
            if (oldCap > 0) {
                if (oldCap >= MAXIMUM_CAPACITY) {
                    threshold = Integer.MAX_VALUE;
                    return oldTab;
                }
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
                    newThr = oldThr << 1; // double threshold
            }
            //不成立,跳过
            else if (oldThr > 0) // initial capacity was placed in threshold
                newCap = oldThr;
            else {     
                //程序来到这
                //得到默认为数组指定的大小 16
                newCap = DEFAULT_INITIAL_CAPACITY;
                //得到默认为数组指定的临界点下标 16 * 0.75 = 12
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            }
            //不成立,跳过
            if (newThr == 0) {
                float ft = (float)newCap * loadFactor;
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                          (int)ft : Integer.MAX_VALUE);
            }
            threshold = newThr;
            @SuppressWarnings({"rawtypes","unchecked"})
            //创建一个长度为16的Node类型数组——Node对象可形成链表
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            //将创建的数组赋予Map中的table数组
            table = newTab;
            /*...省略代码段...*/
            return newTab;
        }
    }

    补充:

    当链表的节点到8个并且数组的长度>=64,链表会转为红黑树;

    如果节点>8,但是数组的长度<64,则先对数组进行扩容,达到64时,会重新计算hash值并将数据结构转为红黑树;

    Map中存储的元素在达到临界值的时候,会自动*2扩容;这里的元素指Map中元素的总个数,不单单是数组中的元素,也包括链表中的元素。


    END

上一篇:React中使用ckplayer


下一篇:LeetCode-不含有重复字符的最长子串的长度