JDK13-HashSet-从源码分析如何去重复,以及重写equals和hashcode方法,equals和==的区别

HashSet 是 AbstractSet的子类

这里使用泛型编程
HashSet有一个成员是private transient HashMap<E,Object> map;(不被序列化)
HashSet的构造函数都是使用HashMap的各种构造方式对map变量进行初始化。
HashSet的size、isempty等函数都是直接调用HashMap里面的

从源码上分析:
为什么Set 接口实例存储的是无序的,不重复的数据。
而List 接口实例存储的是有序的,可以重复的元素。

以HashMap为例,key是不能重复的,因为key相当于是map的索引,但是value是可以重复的,如果要put的key,已经有了,key不变,value会更新。
set就是使用HashMap的构造函数进行构造,使用HashMap的putVal函数进行put。

public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);//AbstractCollection类的一个方法
    }
public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        for (E e : c)
            if (add(e))//重点就在于add函数
                modified = true;
        return modified;
    }

HashSet重写了add函数

public boolean add(E e) {
        return map.put(e, PRESENT)==null;//调用HashMap的put函数
    }

HashMap的put函数,需要传入key和value两个参数

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

传入的value是一个Object对象PRESENT,putVal函数中只是在判断key是否相同。所以set的元素,如果是相同的,则会存储到同一个位置。
putVal函数的解析参考这个链接:
https://blog.csdn.net/weixin_44893585/article/details/103638927

putVal函数判断key是否相同依赖于equals和hashcode方法

要实现自动去重,需要重写equals和hashcode,因为在putVal里面判断元素是否相等就是靠equals和hashcode进行比较。
就是利用下面这一句进行判断,

if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
    e = p;

原来的Node是p,要放入的Node是e,如果p的hash值和要e的hash相同并且p的key==或者equals e的key,则说明Node已经存在了。
是先用hash找到索引,然后在索引那个桶里,用equals逐项比较。

这里的hash值可以看下面函数,是hash(key)的返回值

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

hash函数的返回值是key的hashcode和hashcode带符号右移16位异或的结果。

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

hashCode()函数的返回值取决于key所属的类是怎样实现hashcode的。
常见的类对hashcode的实现见链接:
https://blog.csdn.net/weixin_44893585/article/details/103638755
所以,如果要put的key是自己定义的类,那么要在自己的类里重写hashcode函数,否则会调用默认的hashcode,是c实现的本地方法,如下。

public native int hashCode();

同样的,如果自己写的类不能实现equals函数,那么会调用Object类的equals函数,也就是下面

public boolean equals(Object obj) {
        return (this == obj);
    }

而 == 号对于八种基本数据会比较值的大小,对于其他类的对象,会比较存储地址。同一个类的两个相同的实例对象,会储存在不同的存储空间,如果仅用 == 号来比较,会返回false,所以需要重新写equals方法,是一种判断值相同的办法。
这能才能真正的实现自动去重。

比如下面例子

String s1=new String("hello");
String s2=new String("hello");
System.out.println(s1.equals(s2));//true
System.out.println(s1 == s2);//false

这是因为String类重写了equals方法,如下

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }//首先判断内存地址相同
        if (anObject instanceof String) {
            String aString = (String)anObject;
            if (!COMPACT_STRINGS || this.coder == aString.coder) {
                return StringLatin1.equals(value, aString.value);
            }
        }//COMPACT_STRINGS一直为true
        //所以必须要判断string的编码方式是否相同
        //coder是UTF16或者是LATIN1
        //编码方式相同则调用StringLatin1.equals函数
        return false;
    }

StringLatin1.equals函数是对两个String的value进行比较。JDK9之后,String在构造的时候会把值保存在byte[]里面,为了节省内存。
StringLatin1.equals实现如下:

public static boolean equals(byte[] value, byte[] other) {
        if (value.length == other.length) {
            for (int i = 0; i < value.length; i++) {
                if (value[i] != other[i]) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

所以byte[]的每一个元素都相等,才能判断为value相等。

原创文章 64 获赞 27 访问量 9435

猜你喜欢

转载自blog.csdn.net/weixin_44893585/article/details/103639122