java源码分析之HashSet和LinkedHashSet

学习HashSet的时候需要先了解HashMap,如果不是很了解HashMap的请看HashMap
Java容器类的用途是“保存对象”,分为两类:Map——存储“键值对”组成的对象;Collection——存储独立元素。Collection又可以分为List和Set两大块。List保持元素的顺序,可以有相同的元素,而Set不能有重复的元素。
首先对Set接口进行简要的说明。
存入Set的每个元素必须是惟一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set不保证维护元素的次序。Set与Collection有完全一样的接口。
在没有其他限制的情况下需要Set时应尽量使用HashSet,因为它对速度进行了优化。
下面是HashSet的定义:

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

HashSet继承了AbstractSet,实现了Set接口。其实AbstractSet已经实现Set接口了。AbstractSet继承自AbstractCollection,而AbstractCollection实现了Collection接口的部分方法,而Set接口和Collection接口完全一致,所以AbstractSet只是实现了AbstractCollection没有实现的Set接口的方法和重写了部分AbstractCollection已经实现的方法。
下面是HashSet定义的属性:

    //使用HashMap,利用key-value来存储Set中key
    private transient HashMap<E,Object> map;  
    //定义一个默认的value为object对象
    private static final Object PRESENT = new Object();

为什么会有一个HashMap


    //构造方法一:定义一个默认的HashMap
    public HashSet() {
        map = new HashMap<>();
    }
    // 构造方法二:利用给定的一个集合来初始化(HashMap中已经知道)
    //Math.max((int) (c.size()/.75f) + 1, 16)为:创建HashMap中哈希表的大小,默认最小值为16,装载因子为默认0.75
    // 调用addAll方法将c中的元素添加到HashSet对象中
    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);
    }

     //构造方法四:指定哈希表的容量(默认装载因子0.75)
     public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

     // 构造方法五:构造一个指定初始化容量和负载因子的LinkedHashMap
     //dummy参数被忽略,只是用于区分其他的,包含一个int、float参数的构造方法(方法重载)
     public HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

上面的构造方法都很简单,只有构造方法二中调用了addAll(Collection

    //利用AbstractCollection中定义的方法
    public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        for (E e : c)  //
            if (add(e))
                modified = true;
        return modified;
    }

    //HashSet中重写的方法
    public boolean add(E e) {
    //直接调用HashMap中的方法
        return map.put(e, PRESENT)==null;
    }

看add(E e)方法只是调用了HashMap(构造方法中提供了创建LinkedHashMap的方式,但是LinkedHashMap是继承HashMap的,put方法也是调用HashMap的put方法)的put方法将e当做Key,PERSENT当做Value加入到map中并根据返回值判断是否添加成功。
因为HashMap的put方法在Key已经存在的情况下返回的是对应的Value值,若Key不存在则返回的是null,所以根据返回的是null可以确定新元素被添加到HashSet中了,如果返回的是其他值则说明Key已经存在,即元素已经在HashSet中已经存在,add(E e)返回的结果为false。虽然add(E e)返回false说明了HashSet添加元素失败,但实际上其中的map中的内容已经被替换,原先的值被PERSENT代替。
如果原先的值就是null呢?其实不用考虑这个问题,因为通过HashSet添加的元素,Value的内容都是PERSENT,不会出现null的情况。

    //迭代器(key)
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

    //添加对象的个数
    public int size() {
        return map.size();
    }

    //判断是否为空
    public boolean isEmpty() {
        return map.isEmpty();
    }

    //是否包含对象key
    public boolean contains(Object o) {
        return map.containsKey(o);
    }

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

    //删除key
    //remove(Object o)为什么还要判断结果呢?因为通过HashSet存入的元素,所对应的Value值都是PERSENT,如果传入的o不存在,map的remove方法返回为null,则对应的结果是HashSet的remove操作应该放回false,所以这里根据返回的结果判断是否移除成功。
    public boolean remove(Object o) {
        //删除对象(key)
        return map.remove(o)==PRESENT;
    }

   //清空
    public void clear() {
        map.clear();
    }

LinkedHashSet源码分析
LinkedHashSet具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按照元素的插入次序显示。(LinkedHashMap中还可以按照LRU先后顺序显示),
看LinkedHashSet的内容。

   //调用HashSet中的第五个构造函数
   public LinkedHashSet(int initialCapacity, float loadFactor){                            
            super(initialCapacity, loadFactor, true);
    }

    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }

    public LinkedHashSet() {
        super(16, .75f, true);
    }

    public LinkedHashSet(Collection<? extends E> c) {

        super(Math.max(2*c.size(), 11), .75f, true);
        addAll(c);
    }
}

LinkedHashSet继承自HashSet,HashSet基于HashMap实现,看LinkedHashSet类只是定义了四个构造方法,也没看到和链表相关的内容,为什么说LinkedHashSet内部使用链表维护元素的插入顺序(插入的顺序)呢?
//先看下HashSet的中的第五个构造函数

public HashSet(int initialCapacity, float loadFactor, boolean       dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

如果还有对LinkedHashMap不明白的,请看LinkedHashMap

你可能感兴趣的:(java)