Java HashSet

Java HashSet

HashSet 类实现了Set接口,由一个实际上是HashMap实例的散列表支持。该类不保证集合的迭代顺序,也就是说,该类不保证元素的顺序随时间的推移而不变。这个类允许空元素的存在。该类还为基本操作提供了恒定的时间性能,如添加、删除、包含和大小,假设散列函数在桶中正确地分散了元素,我们将在文章中进一步看到这一点。

HashSet的几个重要特征是

  • 实现了Set接口。
  • HashSet的底层数据结构是Hashtable。
  • 因为它实现了Set接口,所以不允许有重复的值。
  • 你在HashSet中插入的对象并不保证以相同的顺序插入。对象是根据它们的哈希代码插入的。
  • 在HashSet中允许有NULL元素。
  • HashSet也实现了 SerializableCloneable 接口。

HashSet的层次结构 如下。

Java中的HashSet

HashSet扩展了Abstract Set类,并实现了Set、Cloneable和Serializable接口,其中E是这个集合所维护的元素的类型。HashSet的直接已知子类是LinkedHashSet。

现在为了保持恒定的时间性能,迭代HashSet需要的时间与HashSet实例的大小(元素的数量)加上支持HashMap实例的 “容量”(桶的数量)之和成比例。因此,如果迭代性能很重要的话,不要把初始容量设置得太高(或者负载因子太低),这是非常重要的。

  • 初始容量: 初始容量是指创建hashable(HashSet内部使用hashable数据结构)时的桶的数量。如果当前容量已满,桶的数量会自动增加。

  • 负载因子: 负载因子是衡量HashSet在自动增加容量之前允许达到的满度。当哈希表中的条目数超过负载因子和当前容量的乘积时,哈希表会被重新洗牌(也就是重建内部数据结构),这样哈希表就有大约两倍的桶数。

                  Number of stored elements in the table
Load Factor = -----------------------------------------
                        Size of the hash table 

例如: 如果内部容量是16,负载因子是0.75,那么当表中有12个元素时,桶的数量会自动增加。

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

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

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

语法: HashSet的声明

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

其中 E 是存储在HashSet中的元素的类型。

HashSet的内部工作: Set接口的所有类在内部都有Map的支持。HashSet在内部使用HashMap来存储其对象。你一定想知道,在HashMap中输入一个值,我们需要一个键值对,但是在HashSet中,我们只传递一个值。

HashMap中的存储: 实际上,我们在HashSet中插入的值是作为地图对象的一个键,对于它的值,java使用一个常量变量。所以在键值对中,所有的值都是一样的。

HashSet在Java中的实现 doc

private transient HashMap map;

// Constructor - 1
// All the constructors are internally creating HashMap Object.
public HashSet()
{
    // Creating internally backing HashMap object
    map = new HashMap();
}

// Constructor - 2
public HashSet(int initialCapacity)
{
    // Creating internally backing HashMap object
    map = new HashMap(initialCapacity);
}

// Dummy value to associate with an Object in Map
private static final Object PRESENT = new Object();

如果我们看一下HashSet类的add()方法。

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

我们可以注意到,HashSet类的add()方法在内部调用了支持HashMap对象的put()方法,将你指定的元素作为键,常量 “PRESENT “作为其值。 remove()方法也以同样的方式工作。它在内部调用Map接口的remove方法。

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

HashSet不仅可以存储唯一的对象,还可以存储唯一的对象集合 ,如ArrayList, LinkedList, Vector,…等。

实现

// Java program to illustrate the concept
// of Collection objects storage in a HashSet
import java.io.*;
import java.util.*;
  
class CollectionObjectStorage {
    
    public static void main(String[] args)
    {
        // Instantiate an object of HashSet
        HashSet<ArrayList> set = new HashSet<>();
  
        // create ArrayList list1
        ArrayList<Integer> list1 = new ArrayList<>();
  
        // create ArrayList list2
        ArrayList<Integer> list2 = new ArrayList<>();
  
        // Add elements using add method
        list1.add(1);
        list1.add(2);
        list2.add(1);
        list2.add(2);
        set.add(list1);
        set.add(list2);
  
        // print the set size to understand the
        // internal storage of ArrayList in Set
        System.out.println(set.size());
    }
}

输出

1

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

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

HashSet类的构造函数

为了创建一个HashSet,我们需要创建一个HashSet类的对象。HashSet类由各种构造函数组成,允许创建HashSet的可能性。以下是该类中可用的构造函数。

1.HashSet() : 这个构造函数用于建立一个空的HashSet对象,其中默认的初始容量为16,默认的负载因子为0.75。如果我们希望创建一个名字为hs的空HashSet,那么可以这样创建。

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

2.HashSet(int initialCapacity): 这个构造函数用来建立一个空的HashSet对象,其中initialCapacity是在创建对象时指定的。这里,默认的loadFactor仍然是0.75。

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

3.HashSet(int initialCapacity, float loadFactor): 这个构造函数用于建立一个空的HashSet对象,其中initialCapacity和loadFactor在对象创建时被指定。

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

4.HashSet(Collection): 这个构造函数用来建立一个HashSet对象,其中包含来自给定集合的所有元素。简而言之,当需要从任何集合对象转换到HashSet对象时,就会使用这个构造函数。如果我们希望创建一个名字为hs的HashSet,可以这样创建。

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

实现 。

// Java program to Demonstrate Working of HashSet Class
  
// Importing required classes
import java.util.*;
  
// Main class
// HashSetDemo
class GFG {
  
    // Main driver method
    public static void main(String[] args)
    {
  
        // Creating an empty HashSet
        HashSet<String> h = new HashSet<String>();
  
        // Adding elements into HashSet
        // using add() method
        h.add("India");
        h.add("Australia");
        h.add("South Africa");
  
        // Adding duplicate elements
        h.add("India");
  
        // Displaying the HashSet
        System.out.println(h);
        System.out.println("List contains India or not:"
                           + h.contains("India"));
  
        // Removing items from HashSet
        // using remove() method
        h.remove("Australia");
        System.out.println("List after removing Australia:"
                           + h);
  
        // Display message
        System.out.println("Iterating over list:");
  
        // Iterating over hashSet items
        Iterator<String> i = h.iterator();
  
        // Holds true till there is single element remaining
        while (i.hasNext())
  
            // Iterating over elements
            // using next() method
            System.out.println(i.next());
    }
}

输出

[South Africa, Australia, India]
List contains India or not:true
List after removing Australia:[South Africa, India]
Iterating over list:
South Africa
India

对HashSet进行各种操作

让我们看看如何在HashSet上执行一些常用的操作。

操作1: 添加元素

为了向HashSet添加一个元素,我们可以使用add()方法。然而,在HashSet中并不保留插入的顺序。 我们需要注意的是,重复的元素是不允许的,所有重复的元素都会被忽略掉。

例子

// Java program to Adding Elements to HashSet
  
// Importing required classes
import java.io.*;
import java.util.*;
  
// Main class
// AddingElementsToHashSet
class GFG {
  
    // Method 1
    // Main driver method
    public static void main(String[] args)
    {
        // Creating an empty HashSet of string entities
        HashSet<String> hs = new HashSet<String>();
  
        // Adding elements using add() method
        hs.add("Geek");
        hs.add("For");
        hs.add("Geeks");
  
        // Printing all string el=ntries inside the Set
        System.out.println("HashSet elements : " + hs);
    }
}

输出

HashSet elements : [Geek, For, Geeks]

操作2: 移除元素

可以使用remove()方法将值从HashSet中移除。

例子

// Java program Illustrating Removal Of Elements of HashSet
  
// Importing required classes
import java.io.*;
import java.util.*;
  
// Main class
// RemoveElementsOfHashSet
class GFG {
  
    // Main driver method
    public static void main(String[] args)
    {
        // Creating an
        HashSet<String> hs = new HashSet<String>();
  
        // Adding elements to above Set
        // using add() method
        hs.add("Geek");
        hs.add("For");
        hs.add("Geeks");
        hs.add("A");
        hs.add("B");
        hs.add("Z");
  
        // Printing the elements of HashSet elements
        System.out.println("Initial HashSet " + hs);
  
        // Removing the element B
        hs.remove("B");
  
        // Printing the updated HashSet elements
        System.out.println("After removing element " + hs);
  
        // Returns false if the element is not present
        System.out.println("Element AC exists in the Set : "
                           + hs.remove("AC"));
    }
}

输出

Initial HashSet [A, B, Geek, For, Geeks, Z]
After removing element [A, Geek, For, Geeks, Z]
Element AC exists in the Set : false

操作 3: 遍历HashSet

使用iterator()方法遍历HashSet的元素。另外,最著名的是使用增强的for循环。

例子

// Java Program to Illustrate Iteration Over HashSet
  
// Importing required classes
import java.io.*;
import java.util.*;
  
// Main class
// IterateTheHashSet
class GFG {
  
    // Main driver method
    public static void main(String[] args)
    {
  
        // Creating an empty HashSet of string entries
        HashSet<String> hs = new HashSet<String>();
  
        // Adding elements to above Set
        // using add() method
        hs.add("Geek");
        hs.add("For");
        hs.add("Geeks");
        hs.add("A");
        hs.add("B");
        hs.add("Z");
  
        // Iterating though the HashSet using iterators
        Iterator itr = hs.iterator();
  
        // Holds true till there is single element
        // remaining in Set
        while (itr.hasNext())
  
            // Traversing elements and printing them
            System.out.print(itr.next() + ", ");
        System.out.println();
  
        // Using enhanced for loop for traversal
        for (String s : hs)
  
            // Traversing elements and printing them
            System.out.print(s + ", ");
        System.out.println();
    }
}

输出

A, B, Geek, For, Geeks, Z, 
A, B, Geek, For, Geeks, Z, 

HashSet操作的时间复杂度: HashSet的底层数据结构是散列的。因此,HashSet的添加、删除和查找(包含方法)操作的摊销(平均或通常情况下)时间复杂度需要O ( 1)时间。

HashSet中的方法

方法 描述
add(E e) 用于添加指定的元素,如果它不存在,如果它存在则返回false。
clear() 用于删除集合中的所有元素。
contains(Object o) 如果一个元素存在于集合中,返回true。
remove(Object o) 如果该元素存在于集合中,则用于移除该元素。
iterator() 用来返回集合中元素的迭代器。
isEmpty() 用于检查该集合是否为空。如果集合为空,返回true;如果集合为非空,返回false。
size() 用来返回集合的大小。
clone() 用于创建一个集合的浅层拷贝。

从java.util.AbstractSet类继承的方法

方法 描述
equals() 用来验证一个对象与一个HashSet的平等性,并对它们进行比较。只有当两个HashSet包含相同的元素时,该列表才会返回true,而不考虑顺序。
hashcode() 返回这个集合的哈希代码值。
removeAll(collection) 这个方法用于从集合中删除所有存在于集合中的元素。 如果这个集合因调用而发生变化,该方法返回true。

从java.util.AbstractCollection类继承的方法

方法 描述
addAll(collection) 这个方法用于将所述集合中的所有元素追加到现有集合中。 这些元素是随机添加的,不遵循任何特定的顺序。
containsAll(collection) 这个方法用来检查这个集合是否包含了给定集合中的所有元素。 如果集合包含所有的元素,该方法返回true;如果有任何元素丢失,则返回false。
retainAll(collection) 这个方法用于保留集合中的所有元素,这些元素在给定的集合中被提及。 如果这个集合因调用而改变,该方法返回true。
toArray() 这个方法用来形成一个与集合的元素相同的数组。
toString() Java HashSet的toString()方法用于返回HashSet集合中元素的字符串表示。

java.util.Collection接口中声明的方法

方法 描述
parallelStream() 返回一个以该集合为源的可能的并行流。
removeIf(Predicate filter) | 删除这个集合中满足给定谓词的所有元素。
stream() | 返回一个以这个集合为源的顺序流。
toArray(IntFunction generator) | 返回一个包含此集合中所有元素的数组,使用提供的生成器函数来分配返回的数组。

### java.lang.Iterable接口中声明的方法

方法 | 描述
—|—
forEach(Consumer action) | 对Iterable中的每个元素执行给定的动作,直到所有元素都被处理完或者该动作抛出一个异常。

### java.util.Set接口中声明的方法

方法 | 描述
—|—
addAll(Collection c) | 将指定集合中的所有元素添加到这个集合中,如果它们还没有出现的话(可选操作)。
containsAll(Collection c)

如果这个集合包含了指定集合中的所有元素,则返回true。
equals(Object o) 将指定的对象与这个集合进行比较,看是否相等。
哈希码() 返回这个集合的哈希代码值。
removeAll(Collection c) | 从这个集合中删除其包含在指定集合中的所有元素(可选操作)。
retainAll(Collection c)
只保留这个集合中包含在指定集合中的元素(可选操作)。
toArray() 返回一个包含这个集合中所有元素的数组。
toArray(T[] a) 返回一个包含此集合中所有元素的数组;返回的数组的运行时类型是指定数组的类型。

HashSet vs HashMap

基本原理 HashSet HashMap
实现 HashSet实现了Set接口。 HashMap实现了Map接口。
重复值 HashSet不允许有重复的值。 HashMap存储键,值对,它不允许重复的键。如果键是重复的,那么旧的键将被新的值替换。
存储对象时的对象数量 HashSet只需要一个对象add(Object o)。 HashMap需要两个对象put(K key, V Value)来添加一个元素到HashMap对象中。
虚值 HashSet内部使用HashMap来添加元素。在HashSet中,add(Object)方法中传递的参数作为键K,Java内部为add(Object)方法中传递的每个值关联了假值。 HashMap没有任何假值的概念。
存储或添加机制 HashSet内部使用HashMap对象来存储或添加对象。 HashMap内部使用散列法来存储或添加对象。
更快 HashSet的速度比HashMap慢。 HashMap比HashSet快。
插入 HashSet使用add()方法来添加或存储数据。 HashMap使用put()方法来存储数据。
例子 HashSet是一个集合,例如:{1, 2, 3, 4, 5, 6, 7}。 HashMap是一个键->值对(键到值)的映射,例如:{a -> 1, b -> 2, c -> 2, d -> 1}。

HashSet vs TreeSet

基础知识 HashSet TreeSet
速度和内部实现 对于搜索、插入和删除等操作。对于这些操作,它平均需要恒定的时间。HashSet比TreeSet快。HashSet是用一个哈希表实现的。 TreeSet在搜索、插入和删除方面需要O(Log n),比HashSet高。但是TreeSet保持排序的数据。此外,它还支持像higher()(返回至少高的元素)、floor()、 ceiling()等操作。这些操作在TreeSet中也是O(Log n),在HashSet中不支持。TreeSet是用自平衡二进制搜索树(Red-Black Tree)实现的。TreeSet由Java中的TreeMap支持。
排序 HashSet中的元素是没有顺序的。 TreeSet以Java中的Comparable或Comparator方法定义的排序顺序来维护对象。TreeSet的元素默认以升序排序。它提供了几种方法来处理有序的集合,如first(), last(), headSet(), tailSet()等。
空对象 HashSet允许空对象。 为什么呢,因为TreeSet使用compareTo()方法来比较键,而compareTo()会引发java.lang.NullPointerException。
比较 HashSet使用equals()方法来比较Set中的两个对象,用于检测重复的对象。 TreeSet使用compareTo()方法来实现同样的目的。如果equals()和compareTo()不一致,也就是说,对于两个相等的对象,equals应该返回true,而compareTo()应该返回0,那么它将破坏Set接口的契约,并允许在Set的实现中出现重复,比如TreeSet

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程