java集合之HashSet学习

一.HashSet基础学习

1.何为HashSet?
顾名思义,它是哈希集。很明显,它是利用hash原理来存储数据的,hash最大的特点就是无序和无重复性,但查询快。

2.我们为何需要它?
链表和数组可以按照我们的意思排列元素的次序,但是如果我们不知道我们要查找元素的位置,我们就需要遍历数组和链表直到找到为止,可想而知,如果元素很多,那将花费很多的时间。但如果不在意元素的顺序,那么HashSet是个很好的选择。

3.构造方法:

构造方法 描述
HashSet() 构造一个新的空集合; 背景HashMap实例具有默认初始容量(16)和负载因子(0.75)。
HashSet(Collection<? extends E> c) 构造一个包含指定集合中的元素的新集合。
HashSet(int initialCapacity) 构造一个新的空集合; 背景HashMap实例具有指定的初始容量和默认负载因子(0.75)。
HashSet(int initialCapacity, float loadFactor) 构造一个新的空集合; 背景HashMap实例具有指定的初始容量和指定的负因子。

负载因子决定何时对哈希表再哈希,例如负载因子为0.75,当表中超过75%的位置已经填入元素,这个表进行双倍扩增。

4.其他方法学习

方法 描述
boolean add(E e) 将指定的元素添加到此集合(如果尚未存在)。
void clear() 从此集合中删除所有元素。
Object clone() 返回此 HashSet实例的浅层副本:元素本身不被克隆。
boolean contains(Object o) 如果此集合包含指定的元素,则返回 true 。
boolean isEmpty() 如果此集合不包含元素,则返回 true 。
Iterator iterator() 返回此集合中元素的迭代器。
boolean remove(Object o) 如果存在,则从该集合中删除指定的元素。
int size() 返回此集合中的元素数(其基数)。
Spliterator spliterator() 在此集合中的元素上创建late-binding和故障快速 Spliterator 。

使用例子:

package JavaHeXinJiShu;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

public class example9_2 {
    public static void main(String[] args) throws IOException {

        Set<String> words = new HashSet<>(); // 初始容量为16,负载因子为0.75
        long totalTime = 0;

        try (Scanner in = new Scanner("we should working hard"))
        {
            while (in.hasNext())//hashNext()默认以空格为分界符
            {
                String word = in.next();
                words.add(word);//向哈希表中添加元素
               
            }
        }

        Iterator<String> iter = words.iterator();//得到words的遍历器
        for (int i = 1; i <= 20 && iter.hasNext(); i++)
            System.out.println(iter.next());
        System.out.println(". . .");
    }
}

在这里插入图片描述
可以明显看到添加后的遍历顺序是无序的。

二.HashSet是如何被实现的?

1.查看源码我们可以看到:

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

它主要继承了AbstracSet类和实现了Set接口。

2.查看Set接口定义的方法:

boolean	add(E e)
如果指定的元素不存在,则将其指定的元素添加(可选操作)。

boolean	addAll(Collection<? extends E> c)
将指定集合中的所有元素添加到此集合(如果尚未存在)(可选操作)。

voidclear()
从此集合中删除所有元素(可选操作)。

boolean	contains(Object o)
如果此集合包含指定的元素,则返回 trueboolean	containsAll(Collection<?> c)
返回 true如果此集合包含所有指定集合的元素。

boolean	equals(Object o)
将指定的对象与此集合进行比较以实现相等。

int	hashCode()
返回此集合的哈希码值。

boolean	isEmpty()
如果此集合不包含元素,则返回 true 。

Iterator<E>	iterator()
返回此集合中元素的迭代器。

boolean	remove(Object o)
如果存在,则从该集合中删除指定的元素(可选操作)。

boolean	removeAll(Collection<?> c)
从此集合中删除指定集合中包含的所有元素(可选操作)。

boolean	retainAll(Collection<?> c)
仅保留该集合中包含在指定集合中的元素(可选操作)。

int	size()
返回此集合中的元素数(其基数)。

default Spliterator<E>	spliterator()
在此集合中的元素上创建一个 Spliterator 。

Object[] toArray()
返回一个包含此集合中所有元素的数组。

<T> T[]	toArray(T[] a)
返回一个包含此集合中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型。

3.我们知道AbstracSet类主要实现了Set接口,我们下面看看它重点实现了那些方法:

boolean	equals(Object o)//将指定的对象与此集合进行比较以实现相等。

int	hashCode()//返回此集合的哈希码值。

boolean	removeAll(Collection<?> c)//从此集合中删除指定集合中包含的所有元素(可选操作)。

源码也不长:

package java.util;
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {
   
    protected AbstractSet() {
    }

    //将指定的对象与此集合进行比较以实现相等。
    public boolean equals(Object o) {
        if (o == this)
            return true;

        if (!(o instanceof Set))
            return false;
        Collection<?> c = (Collection<?>) o;
        if (c.size() != size())
            return false;
        try {
            return containsAll(c);
        } catch (ClassCastException unused)   {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }
    }

 //返回此集合的哈希码值。
    public int hashCode() {
        int h = 0;
        Iterator<E> i = iterator();
        while (i.hasNext()) {
            E obj = i.next();
            if (obj != null)
                h += obj.hashCode();
        }
        return h;
    }

   //从此集合中删除指定集合中包含的所有元素(可选操作)。
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        boolean modified = false;

        if (size() > c.size()) {
            for (Iterator<?> i = c.iterator(); i.hasNext(); )
                modified |= remove(i.next());
        } else {
            for (Iterator<?> i = iterator(); i.hasNext(); ) {
                if (c.contains(i.next())) {
                    i.remove();
                    modified = true;
                }
            }
        }
        return modified;
    }

}

4.OK我们了解了HashSet的依赖类,下面我们来分析HashSet类:

1)我们首先学习一个HashSet是如何被建立的:

 Set<String> words = new HashSet<>();

通过上面简单的语句我们创建了一个名为words的HashSet的对象,
实际上它背后是这样实现的:
我们知道创建一个实例调用的是构造方法,以上面的空参数构造方法为例,查询源码我们有:

 private transient HashMap<E,Object> map;
public HashSet() {
        map = new HashMap<>();
    }

从上面来看HashSet里面用到了HashMap,下面我们进一步探索:

 static final float DEFAULT_LOAD_FACTOR = 0.75f;
 public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

从上面我们可以看出这里map的负载因子为0.75,也就是设置哈希表的负载因子。

2)然后我们来看add方法:

 public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
    
  public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

从上面看到,向HashSet中添加元素实际上是利用HashMap来存储元素的。

到此我们可以看出HashSet的基本实现元素了,至于更深入的方法探索,我们可以上面的基础上来探索。

猜你喜欢

转载自blog.csdn.net/c1776167012/article/details/106538680