HashSet基础

915 阅读8分钟

基础

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

特性:

  • 实现Set 接口,是一个 HashMap 实例
  • 底层数据结构是 Hashtable
  • 不允许出现重复值
  • 不保证集合元素的顺序。对象是根据它们的哈希码插入的
  • 允许空元素
  • add, remove, contains, sizeO(N) 的时间复杂度
  • 实现了 SerializableCloneable 接口

HashSet 结构

迭代一个HashSet所需要的时间与, HashSet实例 的 size(元素个数) 加上 HashMap实例的capacity容量(buckets个数,相当于格子数) 的和,成正比。


初始容量(Initial Capacity)

为了保证性能,Hashtable在创建时的初始容量不要设置太高。如果当前size已经满了,容量会自动增加。

HashSet —— size

Hashtable —— capacity

HashSet使用Hashtable作为数据结构。

当HashSet的元素个数到达Hashtable的容量时,Hashtable容量会自动增加。

负载因子(Load Factor)

负载因子是衡量 HashSet 在其容量自动增加之前允许获得多满的度量。 当哈希表中的条目数超过负载因子和当前容量的乘积时,重新哈希表(即重建内部数据结构),使哈希表具有大约两倍的桶数。

负载因子 = size / capacity

例如,

capacity = 16, 负载因子 = 0.75,那么当HashSet的元素个数到12个时,capacity(也就是buckets个数) 就会自动增加。

对性能的影响

负载因子和初始容量是影响HashSet操作性能的两个主要因素。 0.75 的负载因子在时间和空间复杂度方面提供了非常有效的性能。如果我们增加负载因子值超过这个值,那么内存开销会减少(因为它会减少内部重建操作)但是,它会影响哈希表中的添加和搜索操作。为了减少重新哈希操作,我们应该明智地选择初始容量。如果初始容量大于最大条目数除以负载因子,则不会发生重新哈希操作。

注意:HashSet 中的实现是不同步的,即如果多个线程同时访问一个哈希集,并且至少有一个线程修改了该集,则必须在外部进行同步。这通常是通过同步一些自然封装集合的对象来实现的。如果不存在这样的对象,则应使用 Collections.synchronizedSet 方法“包装”该集合。这最好在创建时完成,以防止对集合的意外不同步访问,如下所示:

Set s = Collections.synchronizedSet(new HashSet(...));

HashSet 类的构造方法

1. HashSet():

创建一个空的 HashSet 对象,它的默认初始容量(initial capacity) 是16,默认负载因子(load factor) 是0.75

HashSet<E> hs = new HashSet<E>();

2. HashSet(int initialCapacity):

创建一个空的 HashSet 对象,指定初始容量,默认负载因子(load factor) 是0.75

HashSet<E> hs = new HashSet<E>(int initialCapacity);

3. HashSet(int initialCapacity, float loadFactor):

创建一个空的 HashSet 对象,指定初始容量和负载因子

HashSet<E> hs = new HashSet<E>(int initialCapacity, float loadFactor);

4. HashSet(Collection):

此构造函数用于构建一个包含给定集合中所有元素的 HashSet 对象。 简而言之,当需要从任何 Collection 对象到 HashSet 对象进行任何转换时,将使用此构造函数。

HashSet<E> hs = new HashSet<E>(Collection C);

HashSet 操作方法

Add

/**
* Adds the specified element to this set if it is not already present. 
* More formally, adds the specified element e to this set if this set contains no element e2 
* such that (e==null ? e2==null : e.equals(e2)). 
* If this set already contains the element, the call leaves the set unchanged and returns false.
*/
boolean add = hs.add("apple");   

如果集合中不包含这个元素,则将该元素加入进去,返回 true 。否则,保持集合元素不变,返回 false .

示例:

HashSet<String> hs = new HashSet<>();

hs.add("India");
hs.add("Australia");
hs.add("South Africa");
hs.add("India"); // 添加重复元素

System.out.println(hs);  

输出的结果为:

[South Africa, Australia, India]

可以看到,重复元素 India 只被添加了一次,并且集合内元素不是按添加顺序排列的。

Remove

还是上面的集合,移除 Australia ,看看会发生什么吧:

HashSet<String> hs = new HashSet<>();

hs.add("India");
hs.add("Australia");
hs.add("South Africa");
hs.add("India"); // 添加重复元素
System.out.println(hs);

hs.remove("Australia");  // 移除元素
System.out.println("移除 Australia 之后的列表: " + hs);

System.out.println("元素 Japan 是否存在于集合中 : "+ hs.remove("Japan"));

输出的结果为:

[South Africa, Australia, India]

移除 Australia 之后的列表: [South Africa, India]

元素 Japan 是否存在于集合中 : false

Iterator

HashSet<String> hs = new HashSet<>();
hs.add("India");
hs.add("South Africa");

// Iterating though the HashSet
Iterator itr = hs.iterator();
while (itr.hasNext())
    System.out.print(itr.next() + ", ");

// 更简洁的 for 循环
for (String s : hs)
    System.out.print(s + ", ");

输出的结果为:

// Iterator
South Africa, 
India, 

// for
South Africa, 
India, 

HashSet 唯一对象集合

HashSet 不仅存储唯一的对象,还存储唯一的对象集合,如 ArrayList<E>LinkedList<E>Vector<E>等。

示例:

HashSet<ArrayList> set = new HashSet<>();
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);

ArrayList<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);

set.add(list1);
set.add(list2);

System.out.println(set.size());
System.out.println(set);

输出的结果为:

1
[[1, 2]]

可以看到,当 list1 与 list2 中的元素完全相同时,HashSet 也只会插入一个数组,保持唯一性。

我们改一下代码,将list1与list2 各添加一个不同的数字:

list1.add(3);

list2.add(4);

结果:

2
[[1, 2, 3], [1, 2, 4]]

在存储对象之前,HashSet 使用 hashCode()equals() 方法检查是否存在现有条目。 在上面的例子中,如果两个列表具有相同顺序的相同元素,则它们被认为是相等的。 当您在两个列表上调用 hashCode() 方法时,它们都会给出相同的哈希值,因为它们是相等的。

HashSet 不存储重复项,如果给出两个相等的对象,那么它只存储第一个,这里是 list1。

HashSet 键值对

在HashMap中,我们需要插入一个键值对,而在HashSet中,好像只要加入一个值就可以了,其实,在HashSet里,我们插入的值作为 ****key ,Java使用一个常量作为 value ,所以在键值对中,所有值都是一样的。

HashSet 源码

所有的构造函数都在内部创建 HashMap

public HashSet() {
    this.map = new HashMap();
}

public HashSet(int capacity) {
    this.map = new HashMap(capacity);
}

public HashSet(int capacity, float factor) {
    this.map = new HashMap(capacity, factor);
}

public HashSet(Collection<? extends E> var1) {
    this.map = new HashMap(Math.max((int)((float)var1.size() / 0.75F) + 1, 16));
    this.addAll(var1);
}

与 Map 中的对象关联的虚拟值

private static final Object PRESENT = new Object();

add:

输入的值作为 key, 常量PRESENT 作为 value

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

remove:

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

查看 map.remove 的源码可以看到,返回 nullvalue 值:

public V remove(Object var1) {
    HashMap.Node var2;
    return (var2 = this.removeNode(hash(var1), var1, (Object)null, false, true)) 
        == null ? null : var2.value;
}

所以用 移除var1 后的返回值与 PRESENT 比较,如果这个值存在,那么肯定存在 value = PRESENT,则返回true;否则不存在的话,null 不等于 PRESENT,返回false.

contains:

public boolean contains(Object var1) {
    return this.map.containsKey(var1);
}

hashmap

 public boolean containsKey(Object var1) {
     return this.getNode(hash(var1), var1) != null;
 }

isEmpty:

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

hashmap

public boolean isEmpty() {
    return this.size == 0;
}

\

HashSet 时间复杂度

HashSet 的底层数据结构是哈希表。 因此,HashSet 的 添加、删除和查找(contains)操作的分摊(平均或通常情况)时间复杂度需要 O(1) 时间。

HashSet vs HashMap

BASISHashSetHashMap
实现HashSet 实现 Set 接口HashMap 实现 Map 接口
重复性不允许重复元素HashMap 存储键值对,不允许重复key。 如果 key 重复,新value会替代旧value.
Number of objects during storing objects**存储对象时需要的对象数仅需要一个对象add(Object o).HashMap 添加元素需要两个对象put(K key, V Value)
Dummy value**虚设的值HashSet 内部使用 HashMap 储存键值对,输入的元素作为key, java给定一个虚设的常量值作为value。HashMap 没有 Dummy value 这个概念
Storing or Adding mechanismHashSet internally uses the HashMap object to store or add the objects.HashMap internally uses hashing to store or add objects
FasterHashSet 比 HashMap 慢.HashMap 比 HashSet 快.
InsertionHashSet 使用 add()HashMap 使用 put()
ExampleHashSet is a set, e.g. {1, 2, 3, 4, 5, 6, 7}.HashMap is a key -> value pair(key to value) map, e.g. {a -> 1, b -> 2, c -> 2, d -> 1}.

HashSet vs TreeSet

BASISHashSetTreeSet
Speed and internal implementation**速度和内部实现对于 search, insert and delete 操作,HashSet 使用常量时间 O(1)。HashSet 比 TreeSet 更快。HashSet 是使用哈希表实现的。对于 search, insert and delete 操作,TreeSet 需要 O(logN).但是 TreeSet 保持排序的数据。 此外,它还支持诸如higher()(返回最少的更高元素)、floor()、ceiling() 等操作。这些操作在TreeSet 中也是O(Log n),而在HashSet 中不支持。 TreeSet 是使用自平衡二叉搜索树(红黑树)实现的。 TreeSet 由 Java 中的 TreeMap 支持。
排序不按顺序TreeSet 以 Java 中的 Comparable 或 Comparator 方法定义的 Sorted 顺序维护对象。 默认按升序排序。 它提供了几种处理有序集合的方法,如 first()、last()、headSet()、tailSet() 等。
Null Object允许空元素TreeSet 不允许空元素,并且会抛出空指针异常。因为TreeSet使用 compareTo() 比较keys,而 compareTo() 会抛空指针异常。
Comparison**比较HashSet 使用 equals() 方法比较 Set 中的两个对象并检测重复项。TreeSet 使用 compareTo() 比较。如果 equals() 和 compareTo() 不一致,例如,对两个相同的对象, equals() 应该返回true,而 compareTo() 应该返回 0,这会打破对Set接口的约定,并且允许Set实现中出现重复值,例如 TreeSet.

HashSet Methods

add(E e)Used to add the specified element if it is not present, if it is present then return false.
clear()Used to remove all the elements from set.
contains(Object o)Used to return true if an element is present in set.
remove(Object o)Used to remove the element if it is present in set.
iterator()Used to return an iterator over the element in the set.
isEmpty()Used to check whether the set is empty or not. Returns true for empty and false for a non-empty condition for set.
size()Used to return the size of the set.
clone()Used to create a shallow copy of the set.

java.util.AbstractSet

equals()Used to verify the equality of an Object with a HashSet and compare them. The list returns true only if both HashSet contains same elements, irrespective of order.
hashcode()Returns the hash code value for this set.
removeAll(collection)This method is used to remove all the elements from the collection which are present in the set.This method returns true if this set changed as a result of the call.

java.util.AbstractCollection

addAll(collection)This method is used to append all of the elements from the mentioned collection to the existing set.The elements are added randomly without following any specific order.
containsAll(collection)This method is used to check whether the set contains all the elements present in the given collection or not.This method returns true if the set contains all the elements and returns false if any of the elements are missing.
retainAll(collection)This method is used to retain all the elements from the set which are mentioned in the given collection.This method returns true if this set changed as a result of the call.
toArray()This method is used to form an array of the same elements as that of the Set.
toString()The toString() method of Java HashSet is used to return a string representation of the elements of the HashSet Collection.

java.util.Collection

parallelStream()Returns a possibly parallel Stream with this collection as its source.
removeIf(Predicate<? super E> filter)Removes all of the elements of this collection that satisfy the given predicate.
stream()Returns a sequential Stream with this collection as its source.
toArray(IntFunction<T[]> generator)Returns an array containing all of the elements in this collection, using the provided generator function to allocate the returned array.

java.lang.Iterable

forEach(Consumer<? super T> action)Performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception.

java.util.Set

addAll(Collection<? extends E> c)Adds all of the elements in the specified collection to this set if they’re not already present (optional operation).
containsAll(Collection<?> c)Returns true if this set contains all of the elements of the specified collection.
equals(Object o)Compares the specified object with this set for equality.
hashCode()Returns the hash code value for this set.
removeAll(Collection<?> c)Removes from this set all of its elements that are contained in the specified collection (optional operation).
retainAll(Collection<?> c)Retains only the elements in this set that are contained in the specified collection (optional operation).
toArray()Returns an array containing all of the elements in this set.
toArray(T[] a)Returns an array containing all of the elements in this set; the runtime type of the returned array is that of the specified array.

Resources

引用来源:

HashSet

oracle hashset