学习笔记22 set

一、概述

Set是一种集合类型,可以快速在大量数据中查找特定值。

Set存储无序序列中的元素,并且不允许重复。与列表不同,列表中的数据可以通过索引访问,但是在集合中,元素没有与集合中的位置相关联。

Set是优化了搜索特定元素的数据结构,是查找特定元素时比较适用的一种集合类型。Set与List的区别是,Set不允许重复元素,而且不能在指定位置插入元素

二、hashset(无顺序)

1.概念

HashSet是Set接口的一个具体实现,它使用哈希码(hash codes)将元素存储在集合中,从而加快搜索速度。

哈希码是一个整数值,代表一个对象的特征,有些类似于识别码(但不一定是唯一的)。使用哈希算法可以为对象计算哈希码。理想情况下,不同的对象产生的哈希码不同,相同对象的哈希码相同。

Java中的每个对象都有一个hashCode方法,其返回一个哈希码。然而,从Object类继承的hashCode方法通常不够用,因为它返回的值基于对象的内存地址,而不是对象的值,可能会有相同值的不同对象返回不同的哈希码。因此,通常需要重写hashCode方法,以返回等价对象的相同哈希码。

当向HashSet中添加一个对象时,HashSet会调用该对象的hashCode方法获取其哈希码,并检查集合中是否已经存在具有相同哈希码的对象。如果已经存在,则调用该对象的equals方法进一步判断两个对象是否相等。如果相等,则HashSet不会将该对象添加到集合中。

为啥检查过hash code还要用equal判断是否相等呢?因为哈希算法可能会存在冲突,即不同的对象可能会产生相同的哈希码,因此HashSet在判断对象是否相等时,同时使用hashCode方法和equals方法进行判断。如果一个新对象的哈希码与集合中已有的对象的哈希码相同,但是两个对象的值不同,HashSet会允许该新对象添加到集合中。

在HashSet中,每个对象的哈希码对应着一个桶(bucket),所有具有相同哈希码的对象都被存储在相同哈希码的桶中。有些桶中只存储一个对象,而其他桶中则可能存储多个对象。

Load factor是指哈希表中需要被占用的桶(buckets)的百分比,在这个百分比被占用时,哈希表的容量将会被增加。例如,如果一个哈希表的load factor为0.75,那么当75%的桶被占用时,哈希表的容量就会被增加。

2.构造器

3.add方法 

import java.util.*;

/**
 * This program demonstrates how to add elements to a HashSet.
 * It also shows that duplicate elements are not allowed.
 */
public class HashSetDemo1 {
    public static void main(String[] args) {
        // Create a HashSet to hold String objects.
        Set<String> fruitSet = new HashSet<>();

        // Add some strings to the set.
        fruitSet.add("Apple");
        fruitSet.add("Banana");
        fruitSet.add("Pear");
        fruitSet.add("Strawberry");

        // Display the elements in the set.
        System.out.println("Here are the elements.");
        System.out.println(fruitSet);

        // Try to add a duplicate element.
        System.out.println("\nTrying to add Banana to the set again . . .");
        if (!fruitSet.add("Banana")) {
            System.out.println("Banana was not added again.");
        }

        // Display the elements in the set.
        System.out.println("\nHere are the elements once more.");
        System.out.println(fruitSet);
    }
}
Here are the elements.
[Apple, Pear, Strawberry, Banana]
Trying to add Banana to the set again...
Banana was not added again.
Here are the elements once more.
[Apple, Pear, Strawberry, Banana]

HashSet类的add方法在添加重复项时不会抛出异常,只是不会添加该项。但是该方法会返回一个布尔值,指示该项是否已添加。上面的语句调用了add方法,将"Banana"作为参数传递。由于"Banana"已经在集合中,该方法返回false,表示没有再次添加它。 

4.自己的hashCode Method

有时候我们不希望用object类的hashcode方法,希望重写hashcode方法,需要注意:

1.如果多次调用一个对象的hashCode方法,该方法应始终返回相同的值。
2. 如果equals方法报告两个对象相等,则两个对象的hashCode方法都应该返回相同的值。
3. 如果equals方法报告两个对象不相等,则两个对象具有相同的哈希码是允许的。但不能有太多不相等的对象具有相同的哈希码,因为这样会降低使用哈希码进行搜索的算法的性能。

下面看个例子,这个例子中我们重写了equals方法,但没有重写hashcode 方法,因为这里不可能多次调用  值相同 的 不同对象,也就是说:如果对象A B的值相同,那么B对象一定就是A对象(不可能是和A对象值相同的另一个对象,地址和对象是一一对应的,相同的对象不可能有多个地址),既 B对象的地址就是A对象的地址,所以我们可以使用默认的hashcode 方法。

public class Car {
    private String vin;         // 车辆识别号
    private String description; // 车辆描述
    
    /**
     * 构造函数
     * @param v VIN
     * @param desc 车辆描述
     */
    public Car(String v, String desc) {
        vin = v;
        description = desc;
    }
    
    /**
     * 获取车辆识别号
     */
    public String getVin() {
        return vin;
    }
    
    /**
     * 获取车辆描述
     */
    public String getDescription() {
        return description;
    }
    
    /**
     * 返回包含VIN和描述的字符串
     */
    public String toString() {
        return "VIN:" + vin + "\tDescription:" + description;
    }
    
    /**
     * 返回此车辆的哈希码
     */
    public int hashCode() {
        return vin.hashCode();
    }
    
    /**
     * 比较两个对象是否相等
     */
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof Car))
            return false;
        else {
            Car tempCar = (Car) obj;
            return vin.equalsIgnoreCase(tempCar.vin);
        }
    }
}
import java.util.*;

public class CarHashSet {
    public static void main(String[] args) {
        // 创建一个HashSet用于存储Car对象
        Set<Car> carSet = new HashSet<>();

        // 向HashSet中添加一些Car对象
        carSet.add(new Car("227H54", "1997 Volkswagen"));
        carSet.add(new Car("448A69", "1965 Mustang"));
        carSet.add(new Car("453B55", "2007 Porsche"));
        carSet.add(new Car("177R60", "1980 BMW"));

        // 打印HashSet中的元素
        System.out.println("Here are the cars in the set:");
        for (Car c : carSet) {
            System.out.println(c);
        }
        System.out.println();

        // 搜索一个特定的车辆。这个车辆在集合中。
        Car mustang = new Car("448A69", "1965 Mustang");
        System.out.println("Searching for " + mustang);
        if (carSet.contains(mustang)) {
            System.out.println("The Mustang is in the set.");
        } else {
            System.out.println("The Mustang is NOT in the set.");
        }

        // 搜索另一辆车。这辆车不在集合中。
        Car plymouth = new Car("911C87", "2000 Plymouth");
        System.out.println("Searching for " + plymouth);
        if (carSet.contains(plymouth)) {
            System.out.println("The Plymouth is in the set.");
        } else {
            System.out.println("The Plymouth is NOT in the set.");
        }
    }
}
Here are the names in the set.
Chris
Katherine
David
Kenny
Katherine is in the set.
Bethany is NOT in the set.

三、LinkedHashSet Class(根据传入的顺序排序)

你可能已经注意到HashSet元素显示的顺序与它们添加到集合中的顺序不匹配。集合没有元素的内部位置概念,也不会跟踪插入的顺序。当您遍历一个HashSet时,不应该期望迭代器按照它们插入的顺序返回元素。

如果您需要按照插入的顺序访问集合元素,则可以使用LinkedHashSet类而不是HashSet。LinkedHashSet类是HashSet的扩展,它保持一个内部链接列表,引用按照它们被插入的顺序排列的集合元素。这使您能够按照插入的顺序遍历集合。

四、 TreeSet Class(根据key的大小排序)

tree set会按大小顺序存放element。而这里的大小顺序指的是 “1小于2” 或 实现了Comparable interface的对象。

如果我们使用TreeSet的无参数构造器,TreeSet会自动进行排序。

当然,对于没有实现Comparable interface的对象,如果我们可以修改这个对象的代码,那么我们可以让这个对象实现Comparable interface;如果我们不能修改这个对象本身,我们也可以让这个对象实现Comparator interface ,也就是创建一个ComparatorComparator接受两个参数,然后进行比较。

 Comparator interface:

 对于之前的car例子,如果我们希望按照vin顺序来排序车辆:

import java.util.Comparator;

public class CarComparator implements Comparator<Car> {
    public int compare(Car car1, Car car2) {
        // 获取两个车辆的VIN
        String vin1 = car1.getVin();
        String vin2 = car2.getVin();

        // 比较VIN并返回比较结果
        return vin1.compareToIgnoreCase(vin2);
    }
}

 注意,这里我们没有实现Comparator的equal方法,所以我们就会自动地调用object类的equal方法(通过地址比较)。

import java.util.*;

public class TreeSetDemo2 {
    public static void main(String[] args) {
        // 创建一个TreeSet并将CarComparator的实例传递给它
        SortedSet<Car> carSet = new TreeSet<>(new CarComparator());

        // 将一些Car对象添加到TreeSet中
        carSet.add(new Car("227H54", "1997 Volkswagen"));
        carSet.add(new Car("453B55", "2007 Porsche"));
        carSet.add(new Car("177R60", "1980 BMW"));
        carSet.add(new Car("448A69", "1965 Mustang"));

        // 打印按VIN顺序排序的TreeSet中的元素
        System.out.println("Here are the cars sorted in order of their VINs:");
        for (Car car : carSet) {
            System.out.println(car);
        }
    }
}

Here are the cars sorted in order of their VINs:

VIN: 177R60 Description: 1980 BMW

VIN: 227H54 Description: 1997 Volkswagen

VIN: 448A69 Description: 1965 Mustang

VIN: 453B55 Description: 2007 Porsche

treeSet构造器:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值