JDK源码解析之HashSet

前言:

    集合类型三巨头:List、Set、Map

    在之前的博客中已经讲了关于List的实现类、Map的实现类,现在还有Set相关的没有介绍过了。

    为什么先介绍Map后介绍Set呢?按理说Set应该比Map的实现更简单点啊?

    

    当然,这是笔者的一点小心思,看过源码的都知道,Set是基于Map来实现的,所以想了解Set,必须先了解Map    

    对Map不太了解的同学可以先看一下笔者另一篇博客:https://blog.csdn.net/qq_26323323/article/details/86219905 

 

1.HashSet结构分析

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{

    // 一个map作为其成员变量
    private transient HashMap<E,Object> map; 
    
    // 后面会用到,每一个HashSet唯一的PRESENT
    private static final Object PRESENT = new Object();
    

2.HashSet构造方法

// 1.空参构造
public HashSet() {
    // 初始化map
    map = new HashMap<>();
}

// 2.使用一个集合进行初始化加载
public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}

    构造方法主要还是围绕创建一个HashMap

 

3.添加操作

// HashSet.add()
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

    我们来详细分析一下这个添加方法

    需要回顾一下HashMap的put方法,e作为map的key,如果map已经存在该key,则比较value(也就是当前的PRESENT),如果value也相同则执行update操作,如果不同,则将这个value添加到链表尾。

    我们来分两种情况来分析这个add方法

    1)之前没有添加过e

        则会被顺利添加到map中,value为PRESENT,此时map.put()方法返回的是null,null==null返回的是true,则整个hashSet.add()方法返回true

 

    2)之前已经添加过e

        map.put()方法,发现key所在index已经有数据,进行value的比较操作,发现两个value也是同一个value,则执行update操作,put方法返回PRESENT,PRESENT!=null,则整个hashSet.add()方法返回false

 

    这就是HashSet数据不重复的原因

 

4.删除操作

public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
}

    map.remove()方法笔者不再详述,可以参考之前的文章。

    map.remove()方法返回该key(也就是Object o)所关联的value,

    * 如果HashSet已经添加过该key,则value为PRESENT,整个HashSet.remove()方法返回true;

    * 如果HashSet没有添加过该key,则value为null,整个HashSet.remove()方法返回false;

 

5.查询操作

    Set集合的查询只能用来查询当前值是否存在

public boolean contains(Object o) {
    return map.containsKey(o);
}

    也是通过map来实现,查看Object o这个key有没有对应的value存储在Map中

 

6.遍历操作

    关于Set,我们更多的是使用其遍历操作,那么遍历是如何实现的呢?这里我们深入一下

    1)HashSet.iterator()

// 1.HashSet.iterator()
public Iterator<E> iterator() {
    // 这里需要先获取keySet
    return map.keySet().iterator();
}

// 2.HashMap.keySet()
public Set<K> keySet() {
    Set<K> ks = keySet;
    if (ks == null) {
        // 主要就是KeySet对象
        // 下面我们来看下创建这个类做了什么
        ks = new KeySet();
        keySet = ks;
    }
    return ks;
}

// 3.new KeySet()
// 主要就是创建这个类
final class KeySet extends AbstractSet<K> {
    public final int size()                 { return size; }
    public final void clear()               { HashMap.this.clear(); }
    // 我们再来看一下iterator方法
    public final Iterator<K> iterator()     { return new KeyIterator(); }
    public final boolean contains(Object o) { return containsKey(o); }
    public final boolean remove(Object key) {
        return removeNode(hash(key), key, null, false, true) != null;
    }
    public final Spliterator<K> spliterator() {
        return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
    }
    public final void forEach(Consumer<? super K> action) {
        Node<K,V>[] tab;
        if (action == null)
            throw new NullPointerException();
        if (size > 0 && (tab = table) != null) {
            int mc = modCount;
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next)
                    action.accept(e.key);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }
}

// 4.new KeyIterator()
final class KeyIterator extends HashIterator
        implements Iterator<K> {
    // 主要就是这个方法nextNode()
    public final K next() { return nextNode().key; }
}

    2)new KeyIterator()

    我们来看一下在创建KeyIterator对象的时候发生了什么

// final class KeyIterator extends HashIterator
// keyIterator没有自己的构造方法,则其在构造时会调用父类HashIterator的空参构造方法
// HashIterator
abstract class HashIterator {
    Node<K,V> next;        // next entry to return
    Node<K,V> current;     // current entry
    int expectedModCount;  // for fast-fail
    int index;             // current slot

    // 就是这个方法
    HashIterator() {
        expectedModCount = modCount;
        // 将HashMap的table赋值到t
        Node<K,V>[] t = table;
        current = next = null;
        index = 0;
        if (t != null && size > 0) { // advance to first entry
            // 初始化第一个Entry
            do {} while (index < t.length && (next = t[index++]) == null);
        }
    }

    3)KeyIterator.nextNode()分析

final Node<K,V> nextNode() {
    Node<K,V>[] t;
    Node<K,V> e = next;
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    if (e == null)
        throw new NoSuchElementException();
    // 
    if ((next = (current = e).next) == null && (t = table) != null) {
        do {} while (index < t.length && (next = t[index++]) == null);
    }
    return e;
}

    如何理解这种遍历方式呢?我们知道HashMap是数组+(链表|红黑树)的方式。

    遍历元素,首先从数组下标为0的节点开始遍历,获取元素e,然后获取e.next,...一直获取到e.next.next...==null为止,说明数组下标为0的链表上所有元素都已经被获取到了。

    然后从数组下标为1的节点开始遍历,继续重复上述动作

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

恐龙弟旺仔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值