java进阶|HashSet的源码分析

任何一项集合容器的使用都有着特定的场景,记得我们刚开始学习HashSet这样的集合时,总会记得那句话"HashSet集合的元素是不重复的",然而,过了很久也是这样去记得,但是为什么是不能重复的?是如何保证不能重复的?却在自己的心里打上一个问号?也就有了这篇文章最初想写的想法了。

一,首先按照以往的风格看下HashSet集合的类继承结构。

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

继承了AbstractSet是为了复用已经提供好的方法,然后实现Set接口实现其增加,删除,判断元素是否存在的方法,实现序列化接口主要在于实现序列化的内容。

二,集合的特点主要是为了装填元素的,在内存中进行数据的装载,便于操作,这是自己的理解,我们先看下HashSet的构造函数,然后再进一步去分析它的方法的实现。

public HashSet() {
        map = new HashMap<>();
    }

new一个HashSet的构造函数,相当于new了一个HashMap键值对集合容器,这也是可以保证HashSet集合是如何保证元素不重复的,关于HashMap的内容,这里先不做内容介绍,因为这里面涉及到的内容太多了,现在分析还有点太早。

三,下面介绍一下HashSet的add()添加元素的方法。

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
  步骤一:
   public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
  步骤二:
  再往下面分析就涉及到HashMap了,这里就不分析了,记住一点HashMap的put方法的过程,
  若已经存在了则直接覆盖,不存在才添加元素进去。

其实HashSet集合添加元素是将元素E当做hashMap的键,于此同时,HashMap毕竟是一个键值对集合,所以对应的value就整成了一个常量进去,由于HashMap的key是不能够重复的,所以这也是为什么HashSet集合是如何保证集合元素不重复的原因了。

private static final Object PRESENT = new Object();

四,集合的添加方法介绍完了,我们继续看下集合的size()方法,这也就是我们常用集合最常用的方法了。

 public int size() {
        return map.size();
    }

调用的依然是map的size()方法,接下来我们看下HashMap的统计元素个数的方法。

 public int size() {
        return size;
    }

看下下面这段话就明白了size变量的作用

 /**
     * The number of key-value mappings contained in this map.
     */
    transient int size;

用来统计键值对集合元素个数的大小的

五,HashSet的size()方法介绍完了之后,继续看下判断某个元素是否包含在集合中的contains()方法。

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

调用的是HashMap的containsKey方法,主要是判断元素o能否在HashMap这样的键值对集合中能否找到对应的key,因为最初添加元素的时候我们是将元素E当做HashMap的键进行设置的,所以就根据元素o和HashMap的键集合进行比对了,若能找到则返回true,否则返回false。

 final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (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;
    }

上面的查找过程,这里就先简单说下吧,首先先判断hash值是否一样

 public boolean containsKey(Object key) {
        return getNode(hash(key), key) != null;
    }


上面的代码是将传入的元素key先进行计算一下它的hash值,然后再调用getNode()方法的实现。然后再根据是否是链表还是树的特点进行循环遍历,若能找到则直接返回对应的元素,若不能找到则直接返回null,然后根据返回的元素与null进行比对,判断是true或者是false。

六,一般我们对集合进行操作之前首先都会判断是否为空的判断,然后再继续下面的元素操作,集合若为空,再继续操作也没什么意思了,这也算作是前置校验的一种吧,接下来继续看下isEmpty()方法是如何实现的。

 public boolean isEmpty() {
        return map.isEmpty();
    }
步骤一:
  public boolean isEmpty() {
        return size == 0;
    }

首先调用map的isEmpty()方法,因为整个实现的过程都是基于map进程操作的,所以自然会复用对应的方法实现,然后判断map的size大小是否为0,若为0则说明键值对集合map的容器是空的,里面没有装载任何元素。

七,由于自己介绍集合的时候主要是偏工作中实用的方法,所以没有用到的或者说不常用的,这里就不过多介绍了,所以这里介绍如何清空对应的元素clear()方法。

 public void clear() {
        map.clear();
    }
步骤一:
public void clear() {
        map.clear();
    }
步骤二:
public void clear() {
        Node<K,V>[] tab;
        modCount++;
        if ((tab = table) != null && size > 0) {
            size = 0;
            for (int i = 0; i < tab.length; ++i)
                tab[i] = null;
        }
    }

上面的操作还是基于map的操作,首先判断map的size()方法是否大于0,然后循环将map集合的每个元素置为null,然后这样垃圾收集器就会去清理元素为null,这也清空了集合的元素,我们的操作都是基于内存操作的。

八,总结,整个HashSet的源码分析到这里就结束了,这是自己在分析源码时用到的一些测试数据。





import java.util.HashSet;
import java.util.Set;


public class TestHashSet {
    public static void main(String[] args) {
        Set<Integer> set=new HashSet<>();
        set.add(1);
        set.add(2);
        set.add(3);
        set.add(4);
        set.add(1);
        System.out.println("set.size() = " + set.size());
        boolean flag = set.contains(1);
        System.out.println("flag = " + flag);
        System.out.println("set = " + set.isEmpty());


    }
}


到这里说下吧,集合的源码分析文章大部分都分析完了,想了解数组,链表以及栈的源码的可以看以往的历史文章,其实看一篇文章的内容不在于分享者是如何实现的,而在于我们读完了之后我们是如何做的,只有自己觉得,哦,这个是对的,它对我有帮助了,我就是觉得这个是好的,而不是长篇大论介绍一番之后,自己没有看到自己需要的内容,文章简短有简短的好处,看自己喜好,到这里就结束了,觉得有用,顺手帮点个在看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值