HashSet--阅读源码从jdk开始

基于HashMap实现

 

HashSet内部实现是基于HashMap的,这是阅读和理解HashSet源码的关键,只要理解了HashMap的实现原理,再来看HashSet的源码就简单了。对于HashMap源码的阅读可以看之前的一篇文章

 

首先看两个重要的成员变量:

private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();//HashMap的value存放的都是这个对象

 

HashSet内部的主要成员变量是一个HashMap,使用这个HashMapkey进行数据存放(利用HashMapkey的不重复,保证HashSet的不可重复),HashMapvalueHashSet没啥意义,所以统一存放的同一个对象PRESENT的引用。

 

构造方法

 

HashSet的构造方法的目的只有一个:实例化自己的HashMap成员变量(实际上就是调用HashMap的各个构造方法进行实例化)。

 

默认构造方法:

public HashSet() {//默认构造方法,实际上调用的HashMap的默认构造方法进行实例化
        map = new HashMap<>();
}

 

 

指定容量的构造方法:

public HashSet(int initialCapacity) {//调用HashMap的指定容量构造方法
        map = new HashMap<>(initialCapacity);
}

 

 

指定容量和增长因子构造方法:

public HashSet(int initialCapacity, float loadFactor) {//调用HashMap的指定容量和增长因子构造方法
        map = new HashMap<>(initialCapacity, loadFactor);
    }

 

 

参数为集合的构造方法

public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));//根据集合的大小,调用HashMap的指定容量和增长因子构造方法
        addAll(c); 实际上循环调用的是HashSet的add方法
    }

 

 

包保护的构造方法

HashSet(int initialCapacity, float loadFactor, boolean dummy) {//该构造方法普通用户无法使用
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

 

这个构造方法只跟HashSet同一个包下的类才可以见,即:java.util包中类才有权使用。目前发现只有在LinkedHashSet的构造方法中调用过该方法,LinkedHashSet是HashSet的子类

 

其他主要方法:

 

HashSet的逻辑处理方法,都是直接调用HashMap的相关方法进行处理。比如add方法调用的HashMapput方法,remove方法调用的HashMapremove 等等:

 

public boolean add(E e) {
    return map.put(e, PRESENT)==null; //hashMap中的每个value都是存放的同一个PRESENT对象的引用
}
 
public boolean remove(Object o) {
        return map.remove(o)==PRESENT; //调用的HashMap的remove方法
    }
 

 

其他方法不再一一列举,都是直接调用HashMap的相对应方法。

 

接下来看看两个特殊的方法。

 

writeObject readObject方法

 

HashSet实现了Serializable序列化接口,我们都知道实现了该接口其实就是可序列化的。重写writeObject readObject方法可以实现自定义序列化。

    

/**
     * 重写的序列化方法
     * @param s
     * @throws java.io.IOException
     */
    private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException {
        // 调用默认序列化方法
        s.defaultWriteObject();
 
        // 序列化HashMap的容量和增长因子
        s.writeInt(map.capacity());
        s.writeFloat(map.loadFactor());
 
        // 序列化HashMap的大小
        s.writeInt(map.size());
 
        // 序列化HashSet的每一个节点
        for (E e : map.keySet())
            s.writeObject(e);
    }
}
 
/**
     * 重写反序列化方法
     * @param s
     * @throws java.io.IOException
     * @throws ClassNotFoundException
     */
    private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
        // 调用默认的序列化方法
        s.defaultReadObject();
 
        // 反序列化容量
        int capacity = s.readInt();
        if (capacity < 0) {
            throw new InvalidObjectException("Illegal capacity: " +
                    capacity);
        }
 
        // 反序列化增长因子.
        float loadFactor = s.readFloat();
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
            throw new InvalidObjectException("Illegal load factor: " +
                    loadFactor);
        }
 
        // 反序列化大小.
        int size = s.readInt();
        if (size < 0) {
            throw new InvalidObjectException("Illegal size: " +
                    size);
        }
 
        // Set the capacity according to the size and load factor ensuring that
        // the HashMap is at least 25% full but clamping to maximum capacity.
        capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
                HashMap.MAXIMUM_CAPACITY);
 
        // 根据以上参数,构造新的HashMap
        map = (((HashSet<?>)this) instanceof LinkedHashSet ?
                new LinkedHashMap<E,Object>(capacity, loadFactor) :
                new HashMap<E,Object>(capacity, loadFactor));
 
        // 反序列化每个节点,并放入HashMap
        for (int i=0; i<size; i++) {
            @SuppressWarnings("unchecked")
            E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
}

 

 

我们再看下HashSetHashMap成员变量定义:private transient HashMap<E,Object> map; transient修饰是不能被序列化的,这里所谓的不能被序列化 是指不能被默认的序列化方法序列化。但是HashSet采用重写writeObject readObject方法,自己对transient成员变量进行序列化和反序列化。

为什么HashSet要自定义序列化规则呢?个人理解:可以从源码上来看,序列化和反序列化里不同的地方,就是HashMap的容量capacity在反序列化时进行了重写计算。由于HashMap的容量是自动增长的,为了避免反序列化后重新生成HashMap空间浪费,需要重新进行容量计算。

 

其实ArrayListHashMapHashSet等他们的主要成员变量都是transien修饰的,都重写了writeObject readObject方法,重写的方式都类似(重新计算容量)。这些类都有一个共同的特征,就是容量会自动增长。

 

当然这只是表面上的理解,其实默认的序列化会遍历整个对象的拓扑关系,会消耗更多的空间和时间,自己实现的序列化方法则会简单得多。

关于java的序列化和反序列化,且听下回分解。

猜你喜欢

转载自moon-walker.iteye.com/blog/2376205