HashSet源码分析

HashSet底层是HashMap实现的,关于HashMap的分析请移步到HashMap源码分析

属性

//底层使用HashMap来实现
private transient HashMap<E,Object> map;
 
//虚拟的Object对象作为HashMap的value
private static final Object PRESENT = new Object();

HashSet底层是使用HashMap实现的,由于HashMap存储的是<Key,Value>键值对,而HashSet不需要Value,所以HashSet内部使用了一个虚拟的Object对象作为底层HashMap的值。

构造方法

public HashSet() {
    //底层会初始化一个空的HashMap,并使用默认初始容量为16和加载因子0.75。
        map = new HashMap<>();
    }
    
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }
 
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }
 
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }
 
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

初始化HashSet时,可以指定初始化容量和负载因子。因为HashSet是由HashMap来实现,如果不指定,则默认值都与HashMap中的一样,即默认初始化容量为16,默认负载因子为0.75。

add方法

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
 
//HashMap的put方法
public V put(K key, V value) {  
    //map为空表时,进行扩充  
    if (table == EMPTY_TABLE) {  
        inflateTable(threshold);  
    }  
    //如果key为null,直接定位到table[0]处,进行处理  
    if (key == null)  
        return putForNullKey(value);  
    //计算key的hash值  
    int hash = hash(key);  
    //根据key的hash,定位key在table中索引  
    int i = indexFor(hash, table.length);  
    //判断key是否存在  
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
        Object k;  
        //如果key已存在,则覆盖原value  
        //【判断key相等】:也就是判断两个Object是否相等  
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
            //暂存旧值
            V oldValue = e.value; 
            //用新值覆盖旧值
            e.value = value;  
            e.recordAccess(this);  
            //返回旧值(方法返回后,可能还要用到旧值)  
            return oldValue;  
        }  
    }//key不存在         
    //修改次数+1  
    modCount++;  
    //添加<k,v>  
    addEntry(hash, key, value, i);  
    return null;  
} 

从源码可以看出,HashSet如果添加了重复的元素,即key已存在时,会进行覆盖操作。 借助该特点,可以使用HashSet实现去重操作。

 

面试题:
1.HashSet的实现原理?

2.Set中元素是无序的

HashSet set=new HashSet();
set.add("a");
set.add("b");
set.add("c");
set.add("d");
System.out.println(set);//结果:[d,b,c,a]

3.HashSet不允许重复

情景0

//两次添加"a"
HashSet set=new HashSet();
System.out.println(set.add("a"));//结果:true
set.add("b");
set.add("c");
set.add("d");
System.out.println(set.add("a"));//结果:false

情景1

//两次分别添加不同的对象。
HashSet set=new HashSet();
 System.out.println(set.add(new People("张三")));//true
System.out.println(set.add(new People("张三")));//true

情景2

//两次都是添加p1
HashSet set=new HashSet();
        
People p1=new People("张三");
System.out.println(set.add(p1));//true
System.out.println(set.add(p1));//false

情景3

//两次分别添加s1,s2(显然,s1和s2是不同的对象)。
HashSet set=new HashSet();
        
String s1=new String("a");
String s2=new String("a");
 
System.out.println(set.add(s1));//true
System.out.println(set.add(s2));//false

4.HashSet如何保证不重复的?

hashset底层用的hashmap实现,hashset实际上是没有value的,只是用了一个虚拟的value(Object PRESENT),每个key的值都是该value。

当要插入一个存在的对象时,hashmap对相同的key则进行value值覆盖操作,所以相当于用新的<key,PRESENT>覆盖掉旧的<key,PRESENT>。所以表面看起来没有插入新的重复元素,也就保证了不重复。

5.HashSet添加元素的过程

①HashCode
当HashSet在添加元素时,会先调用hashCode()方法,判断即将加入的元素的hashCode是否与集合中的元素有相同的,如果没有,则允许添加该元素。如果有相同的,则继续调用equals()方法,如果equals()方法返回true,则表示对象已经加入过了,不允许再添加了。否则,如果equels方法返回false,则允许添加新元素。
②equals

对于两个对象来说,如果使用equals返回true, 则这两个对象的hashcode一定相同。
对于两个对象来说,如果使用equals返回false,则这两个对象的hashcode不一定不相同(可以相同或者不同)。如果不同,可以提高性能。

对于Object类来说,不同的Object对象的hashcode值是不同的(hashCode值表示对象的地址)

String类的hashCode()方法重写了Object类的hashCode()方法,只要两个String对象的内容相同则认为hashCode相同,所以情景3比较特殊。

posted @ 2019-01-19 14:44  静水楼台/Java部落阁  阅读(156)  评论(0编辑  收藏  举报