Java笔记整理 —— Set接口

Set接口

基本介绍 

     注:取出的顺序是固定的,不会变。遍历方法:迭代器,增强for,不能用for循环因为没有索引,也没有get方法。   用HashSet演示。

HashSet

底层是HashMap   使用 Hash + equals 方法

    public HashSet() {
        map = new HashMap<>(); //创建一个HashMap
    }

HashSet的add方法大概思路:

   1. 先获取元素的哈希值(hashCode方法)  2. 对哈希值进行运算,得出一个索引值即为要存放在哈希表中的位置号。 3. 如果该位置上没有其他元素,则直接存放4. 如果该位置上已经有其他元素,则需要进行equals判断,如果相等则不再添加。如果不相等,则以链表形式添加。  

其中PRESENT是一个Object对象,只起到占位的作用。

    key是输入的关键字,value就是PRESENT。hash方法计算出key的哈希值,注意并不是简单调用了hashCode方法,而是与 h>>>16进行了异或,计算的伪哈希值,最终在putVal方法中用 按位与 计算出索引。

 重点是理解 putVal这个方法,源码自己去看,这里只写说明。resize方法用于修改Node数组大小。afterNodeInsertion(evict) 无实际用处,是留给子类实现的方法。

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;  
        //table是HashMap的属性,是放Node的数组。刚开始table为null,resize对tab初始化,扩容到16个空间
        if ((p = tab[i = (n - 1) & hash]) == null) //计算出真正的索引,把对应位置赋给p
            tab[i] = newNode(hash, key, value, null); //如果p为空,说明没有元素,创建一个结点,把内容放进去。
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
        //准备添加的key的hash值与当前索引位置对应链表的首元结点hash值相同。
        //并且满足下面两个条件之一:1. p指向的Node结点的key和准备加入的key是同一个对象
                            2. p指向的Node结点的key用equals方法和准备加入的key比较后相同
        //此时不能加入,e指向p,不做任何处理
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        // 如果p是红黑树,那么调用putTreeVal方法加入
            else {
        // 最后一种情况,说明虽然位置被占,但是与首元结点不相同,找首元结点对应的链表  
                for (int binCount = 0; ; ++binCount) { //开始遍历链表
                    if ((e = p.next) == null) {  
                        p.next = newNode(hash, key, value, null);// 依次和该链表的每一个元素比较后,都不相同(到了最后一个结点),则加入到该链表的最后
                        if (binCount >= TREEIFY_THRESHOLD - 1) // 注意在把元素添加到链表后,立即判断该链表是否已经达到8个结点(也就是>=7)
                            treeifyBin(tab, hash);// 如果已经达到,就调用 treeifyBin()。在这个方法里,要先进行判断 if(tab == null || (n=tab.length)<MIN_TREEIFY_CAPACITY) 也就是看table是否为空或者是否小于64,如果成立,先table扩容。如果不成立才转成红黑树。
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
         // 和该链表的每一个元素比较过程中,如果有相同情况,就直接break(判断条件与上方一致)
                    p = e;
                }
            }
            if (e != null) { // 说明有重复元素(主要针对HashMap,用于覆盖)
                V oldValue = e.value; //记录value,HashSet都是PRESENT,但HashMap是自己定义的
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value; //HashMap要覆盖,HashSet就不用了,因为PRESENT是null
                afterNodeAccess(e);
                return oldValue; //add失败(不是null)
            }
        }
        ++modCount; //记录修改次数
        if (++size > threshold) //threshold在resize方法中,是表的临界值(初始12)
            resize(); //如果size大于临界值,扩容
        afterNodeInsertion(evict);//留给子类实现的方法,对HashMap来说是个空方法
        return null;//返回空 成功
    }

要注意的一个地方:table扩容的两个时机—— 1. 大于临界值  2. 链表加入结点并且大于8个   

if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
    resize(); // treeifyBin的部分代码
              // 这告诉我们,如果链表上已经超过8个结点,但是table还没达到64,那么会先扩容

重写判断是否加入的方法

需要重写Employee类的equals方法和hashCode方法(直接输入equals)

注意这样生成的方法就是重写后的方法,不用更改。

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age && Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() { 
        return Objects.hash(name, age); //name,age等属性都被塞进一个object数组里
    }

       如果类里面有别的类属性(比如A里面有B类的对象),那么B里面也要重写equals和hashCode方法 

LinkedHashSet 

(结点应该放在绿色框里,这里画的不清楚)

1. LinkedHashSet 加入顺序和取出元素的顺序一致

2. LinkedHashSet 底层维护的是一个LinkedHashMap(是HashMap的子类)

3. LinkedHashSet 底层结构—— 数组table + 双向链表

4. 添加第一次时,直接将数组table扩容到16,数组table是 HashMap$Node[]类型,但存放的结点类型是 LinkedHashMap$Entry(多态),是Node的子类(可以从左下角的structure查看)

    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值