Set集合(HashSet集合)

Set集合:一个不包含重复元素的collection。更确切地讲,set不办函满足e1.equals(e2)的元素对e1和e2,并且包含一个null元素。正如其名称所暗示的,此接口模仿了数学上的set抽象。

java.util.Set接口 extends Collection接口

特点:

1.不允许存储重复元素

2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历。

一.HashSet

此类实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set的迭代顺序;特别是它不保证顺序恒久不变。此类允许使用null元素。

此实现也不是同步的

public static void main(String[] args) {
    // 多态写法
    Set<Integer> set = new HashSet<>();
    set.add(1);
    set.add(3); // 无序,存取顺序不一致
    set.add(2);
    set.add(1); // 不能有重复元素
    // 不能使用普通for循环,可以迭代,也可以foreach循环
    Iterator<Integer> iterator = set.iterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next()); // 1 2 3
    }
    for (Integer i : set) {
        System.out.println(i); // 1 2 3
    }
}

哈希集合存存储数据的结构(哈希表)

什么是哈希表呢?

我们先看hash值(哈希值)

定义:是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来的地址,不是数据实际存储的物理地址)

在Object类有一个方法,可以获取对象的哈希值。

public native int hashCode():返回该对象的哈希值。

native:代表该方法调用的是本地操作系统的方法。

public static void main(String[] args) {
    Person person = new Person();
    // Person类继承了Object类,所以可以使用Object类的hashCode方法
    int hash = person.hashCode();

    Person person1 = new Person();
    int hash1 = person1.hashCode();

    /*Object源码里的toString方法:
    public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
    输出的后半部分就是哈希地址值的十六进制表示
     */
    // 对比
    System.out.println(person); // demo08.Person@1b6d3586
    System.out.println(hash); // 460141958
}

注意,Object源码的toString方法,打印的@后半部分就是哈希地址值的十六进制数。

但同样只是逻辑地址,不是物理地址。

逻辑地址一样,不代表物理地址一样!对象不一样,哈希地址也有可能一样。

举例:

System.out.println(person == person1); // false
String str = new String("aaa");
String str1 = new String("aaa");
System.out.println(str.hashCode()); // 96321
System.out.println(str1.hashCode()); // 96321
// 逻辑地址一样不等同于物理地址一样
System.out.println(str == str1); // false

// 对象不一样,哈希地址也有可能一样
System.out.println("重地".hashCode()); // 1179395
System.out.println("通话".hashCode()); // 1179395

在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值一次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的。

如图解释:

下面我们来学习HashSet不允许元素重复的原理:

// 创建HashSet集合
HashSet<String> hashSet = new HashSet<>();
// 存储元素
String str1 = new String("aaa");
String str2 = new String("aaa");
hashSet.add(str1);
hashSet.add(str2);
hashSet.add("重地");
hashSet.add("通话");
hashSet.add("aaa");
System.out.println(hashSet); // [aaa, 重地, 通话]

HashSet存储自定义类型元素

往HashSet里存储Integer、String类型的数据,这些数据类型都是已经定义好的类型,且重写了hashCode方法和equals方法,那么如何存储自定义类型元素呢?

同样重写hashCode方法和equals方法!

给HashSet存放自定义类型元素时,需要重写对象中的hashCode方法和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一。

没有重写方法时:

public static void main(String[] args) {
    // 创建HashSet集合存储Person
    HashSet<Person> hashSet = new HashSet<>();

    Person p1 = new Person("易烊千玺");
    Person p2 = new Person("王俊凯");
    Person p3 = new Person("易烊千玺");
    hashSet.add(p1);
    hashSet.add(p2);
    hashSet.add(p3);

    System.out.println(p1.hashCode()); // 460141958
    System.out.println(p3.hashCode()); // 1956725890
    System.out.println(p1.equals(p3)); // false
    // 没有重写equals和hashCode方法时
    // 打印[Person{name='易烊千玺'}, Person{name='王俊凯'}, Person{name='易烊千玺'}]
    // 无法识别同名人
    System.out.println(hashSet);
}

重写方法之后:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person person = (Person) o;
    return Objects.equals(name, person.name);
}

@Override
public int hashCode() {
    return Objects.hash(name);
}
System.out.println(p1.hashCode()); // 806906957
System.out.println(p3.hashCode()); // 806906957
System.out.println(p1.equals(p3)); // true

System.out.println(hashSet); // [Person{name='王俊凯'}, Person{name='易烊千玺'}]

猜你喜欢

转载自blog.csdn.net/wardo_l/article/details/113990846