正如大家所知,集合的Set接口,大多数都是建立在Map接口上实现的。
向HashSet中add()元素时,判断元素是否存在的依据,不仅需要比较hashcode,还要同时结合equals()方法比较。
HashSet 的add()方法会使用到HashMap的add()方法。可参照源码:
private static final Object PRESENT=new Object();
private transient HashMap<E,Object>map;
public HashSet(){
map=new HashMap<>();
}
public boolean add(E e){
}
HashMap的key是唯一的,由上面的代码可看出,HashSet添加进去的值就是作为HashMap的key。所以不会重复(HashMap比较key是否相等是先比较hashcode
,再比较equals)。
拓展:
HashMap是线程安全吗?
答案肯定是:不是。
例:
- 如果有两个线程T1,T2,同时进行添加数据,又碰巧这两条不同的线程插入的不同的数据经过哈希计算后得到的哈希码一模一样(可延伸阅读哈希碰撞相关知识),而且该位置上还没有被其他数据占用。
- 所以这两个线程可能同时进行,假设一种情况,线程T1通过了if判断,该位置未哈希冲突,进入了if语句的代码部分,还未进行数据插入,这时候CPU就把资源让给了T2,线程T1停在了if语句里,线程T2也判断该位置无哈希冲突,也进入了if语句,线程T2执行完后,轮到T1执行,现在线程T1直接在刚才看到的位置插入而不用再判断。就会出现T1插入的数据将T2之前插入的数据覆盖。导致了T2的数据丢失,发生了线程不安全现象。
- 本来在HashMap中发生哈希冲突是可以用链表法或者红黑树来解决的,但是在多线程中,可能就直接给覆盖了。
- 这种情况之外,在扩容的时候也可能导致数据不一致,因为扩容是从一个数组拷贝到另一个数组。扩容过程:
HashMap的扩容过程,当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值(阈值:当前数组的长度乘以加载因子的值)的时候,就会自动扩容。扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的时候,对象就需要扩大数组长度,以便装下更多元素。当然Java里边的数组是无法通过扩大自身来自动扩容的,方法是使用一个新的数组代替已有的容量不够用的数组。
HashMap hashMap=new HashMap(cap);
cap=2,hashMap的容量为2;
cap=3,hashMap的容量为4;
cap=4,hashMap的容量为4;
cap=5,hashMap的容量为8;
cap=9,hashMap的容量为16;
即若果cap的2的n次方,则容量为cap值,否则为大于cap的第一个2的n次方的值。
(以上内容,部分采自自媒体,侵权则删。)