您的位置:首页 > 编程语言 > Java开发

Java集合容器系列08-HashSet

2018-08-21 15:40 627 查看

一、HashSet的介绍

HashSet是一个依赖于HashMap的Set接口实现,容器的元素存储和操作都是基于内部的一个HashMap实例实现,因为这个原因,它不保证Set中元素的迭代顺序特别是不保证该顺序的恒久不变,允许插入null元素。该类可以为基本的集合操作提供稳定的性能保证,这些基本操作包括add、remove、contains和size,假定哈希函数正确地将元素分布在底层HashMap的槽中,那么对此HashSet进行迭代所需的时间与元素的个数和底层HashMap的槽的个数成正比的,所以迭代性能很重要的话,就不要将初始容量设置得太高(或者负载因子设置得太低)。注意HashSet不是线程安全的容器,如果有多个线程访问该容器,且至少有一个线程对容器做了结构性修改,那么它就必须在外部保证同步,这通常是通过对操作该容器的代码块加锁实现的,如果没有则可以使用Collections.synchronizedSet在包装它作为一个线程安全的容器使用。HashSet的iterator返回的迭代器对象Iterator是fail-fast(快速失败)的,如何理解,即在该迭代器创建之后任何时间对该容器做了结构性修改(除了基于iterator.remve方法删除容器元素之外)都将导致迭代器遍历时抛出ConcurrentModificationException异常,这种快速失败行为无法绝对保证,因此依赖于这个特性编写应用程序是错误的。

二、HashSet的数据结构

public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;

//底层的HashMap实例,这个对象是HashSet的核心
private transient HashMap<E,Object> map;
//HashSet的元素其实就是保存在HashMap实例的KeySet集合里,key/value键值对中的value就直接保存它这个固定的对象
private static final Object PRESENT = new Object();
}

HashSet继承自AbstractSet类,该类实现了一些集合和Set基本操作方法,实现了Set接口在这里几乎起的类似代码注释的功能,因为AbstractSet本身已经实现了Set接口,通过继承它HashSet也间接实现了Set接口,此外HashSet也实现了Clonable接口支持对象的clone()方法拷贝,实现了java.io.Serializable表明该类也支持序列化

三、HashSet源码分析

1 - 构造方法

/**
* 构造函数,底层的HashMap实例默认的初始容量16,负载因子0.75
*/
public HashSet() {
map = new HashMap<>();
}

/**
* 构造函数,在初始化实例时将指定容器的所有元素插入容器中,底层HashMap实例默认负载因子0.75,初始化容量取基于负载
* 因子计算的容器容量和默认初始容量的较大值
*/
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}

/**
* 构造函数,为底层HashMap指定初始容量和加载因子
*/
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}

/**
* 构造函数,为底层HashMap指定初始容量,,加载因子默认0.75
*/
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}

/**
* 构造函数,该方法属于包私有的方法仅被LinkedHashSet使用,dummy参数仅仅只是为了和第三个构造方法(底层是HashMap
* 实例)做区分
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}


2 - boolean isEmpty()方法 - 判空

public boolean isEmpty() {
return map.isEmpty();
}

很简单容器的元素都保存在底层的HashMap实例中,所以直接判断底层HashMap实例是否为空。

3 - boolean add(E e) - 添加元素

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

分析方法源码可知HashSet添加元素实际也是调用底层HashMap的put方法,将元素保存在底层HashMap实例的键值对key/value的key中

4 - boolean remove(Object o)

public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}

一样也是直接调用底层HashMap的remove方法删除元素o

5 - 其他成员方法

//返回容器迭代器,无序
public Iterator<E> iterator() {
return map.keySet().iterator();
}

/**
* 返回容器保存的元素个数
*/
public int size() {
return map.size();
}

/**
* 返回容器是否为空
*/
public boolean isEmpty() {
return map.isEmpty();
}

/**
* 如果容器包含对象o返回true否则返回false
*/
public boolean contains(Object o) {
return map.containsKey(o);
}

public Object clone() {
try {
HashSet<E> newSet = (HashSet<E>) super.clone();
newSet.map = (HashMap<E, Object>) map.clone();
return newSet;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}

/**
* 序列化时调用保存对象状态
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();

// Write out HashMap capacity and load factor
s.writeInt(map.capacity());
s.writeFloat(map.loadFactor());

// Write out size
s.writeInt(map.size());

// Write out all elements in the proper order.
for (E e : map.keySet())
s.writeObject(e);
}

/**
* 反序列时调用
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();

// Read capacity and verify non-negative.
int capacity = s.readInt();
if (capacity < 0) {
throw new InvalidObjectException("Illegal capacity: " +
capacity);
}

// Read load factor and verify positive and non NaN.
float loadFactor = s.readFloat();
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
}

// Read size and verify non-negative.
int size = s.readInt();
if (size < 0) {
throw new InvalidObjectException("Illegal size: " +
size);
}

capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY);

SharedSecrets.getJavaOISAccess()
.checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity));

// Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));

// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}

/**
* 返回一个延迟绑定的迭代器
*/
public Spliterator<E> spliterator() {
return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);
}


6 - HashSet源码总结

HashSet底层实现依赖于对象内部的一个HashMap实例,容器元素最终是以键值对key的形式保存在底层HashMap实例中,HashSet容器的所有操作实际操作的都是底层的HashMap实例
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: