Java 集合框架 HashSet 使用方法

HashSet:

不可重复的字典

  • 实现了Set接口
  • HashSet依赖的数据结构是哈希表
  • 因为实现的是Set接口,所以不允许有重复的值
  • 插入到HashSet中的对象不保证与插入的顺序保持一致。对象的插入是根据它的hashcode
  • HashSet中允许有NULL值
  • HashSet也实现了Searlizable和Cloneable两个接口

拓展:
LinkedHashSet:具有按照插入顺序排序的HashSet,是HashSet 的子类。如果在映射中重新插入键,则插入顺序是不受影响的。

HashSet方法摘要

boolean add(E e) :如果不存在则添加,存在则返回false。
void clear() :移除Set中所有的元素
boolean contains(Object o) :如果这个元素在set中存在,那么返回true。
boolean remove(Object o) :如果这个元素在set中存在,那么从set中删除。
Iterator iterator() :返回set中这个元素的迭代器。
int size() :返回此集合中的元素的数量(集合的容量)。
isEmpty() :如果此集合不包含任何元素,则返回 true。

简单使用:
        HashSet<String> al = new HashSet();
        al.add("data1");
        al.add("data2");
        al.add("data3");
        al.add("data4");
        // 建立迭代器,输出里面的元素
        Iterator it = al.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
        // 也可以使用 foreach 方式输出
        for(String s: al){
            System.out.println(s);
        }
HashSet内部是如何工作的?

所有Set接口的类内部都是由Map做支撑的。HashSet用HashMap对它的内部对象进行排序。你一定好奇输入一个值到HashMap,我们需要的是一个键值对,但是我们传给HashSet的是一个值。

那么HashMap是如何排序的?

实际上我们插入到HashSet中的值在map对象中起的是键的作用,因为它的值Java用了一个常量。所以在键值对中所有的键的值都是一样的。

如果我们在Java Doc中看一下HashSet的实现,大致是这样的:

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()方法,通过你指定的值作为key,常量“PRESENT”作为值传过去。

remove()也是用类似的方法工作。它内部调用的是Map接口的remove。

public boolean remove(Object o)
{
  return map.remove(o) == PRESENT;
}
HashSet操作的时间复杂度:

HashSet底层的数据结构是哈希表,所以HashSet的add,remove与查询(包括contain方法)的分摊(平均或者一般情况)时间复杂度是O(1)。

HashSet中是如何保证元素唯一性呢?

HashSet是通过元素的两个方法,hashCode和equals来完成。
如果元素的hashCode值相同,才会判断equals是否为true,当equals为true时,元素不会被加入
如果元素的hashCode值不同,不会调用equals,顺利添加元素
对于判断元素是否存在,以及删除等操作,依赖的方法是元素的hashCode和equals方法。

具体参见HashSet的部分代码:

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    private transient HashMap<E,Object> map;
 
    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
 
    public HashSet() {
    map = new HashMap<E,Object>();
    }
 
    public boolean contains(Object o) {
    return map.containsKey(o);
    }
 
    public boolean add(E e) {
    return map.put(e, PRESENT)==null;
    }
}

发现HashSet是用HashMap来实现的,利用HashMap中Key的唯一性,保证元素不重复。

由此可见,HashSet中的元素实际上是作为HashMap中的Key存放在HashMap中的。下面是HashMap类中的put方法:

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
        V oldValue = e.value;
        e.value = value;
        e.recordAccess(this);
        return oldValue;
        }
    }
}

从这段代码中可以看出,HashMap中的Key是根据对象的hashCode() 和 euqals()来判断是否唯一的。

为了保证HashSet中的对象不会出现重复值,在被存放元素的类中必须要重写hashCode()和equals()这两个方法。

实现筛选元素重复的例子:

(创建一个Person对象,有年龄和姓名。如果对象的年龄和姓名一致时,则认为是同一个元素。)

import java.util.*;

class Person{
    private String name;
    private int age;
    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }
    public Object getName(){
        return this.name;
    }
    public Object getAge(){
        return this.age;
    }
    public int hashCode(){
        // 返回name的hashCode 加上 年龄的数值 * 39
        // 为什么后面还要 乘一个数? 答:为了确保每个hashCode不同
        // 如果两个数直接相加,有可能不同组合加起来的值是一样的。例:2+6=8 1+7=8
        return this.name.hashCode() + this.age * 39;
    }
    public boolean equals(Object obj){
        Person temp = (Person)obj;
        /*
        * 判断年龄和姓名相等才返回真
        * */
        return(this.age == temp.age && this.name.equals(temp.name));
    }
}

public class test{
    public static void main(String[] args)
    {
        HashSet al = new HashSet();
        al.add(new Person("li1",12));
        al.add(new Person("li1",12));  // 这个元素是重复的
        al.add(new Person("li2",13));
        al.add(new Person("li3",14));
        Iterator it = al.iterator();
        while(it.hasNext()){
            Person each = (Person)it.next();
            System.out.println(each.getName() + ".." + each.getAge());
        }
    }
}

输出结果:

li2..13
li1..12
li3..14
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值