Set是一种集合类型,可以快速在大量数据中查找特定值。
Set存储无序序列中的元素,并且不允许重复。与列表不同,列表中的数据可以通过索引访问,但是在集合中,元素没有与集合中的位置相关联。
Set是优化了搜索特定元素的数据结构,是查找特定元素时比较适用的一种集合类型。Set与List的区别是,Set不允许重复元素,而且不能在指定位置插入元素。
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%的桶被占用时,哈希表的容量就会被增加。
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 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,表示没有再次添加它。
有时候我们不希望用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 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.ChrisKatherineDavidKennyKatherine is in the set.Bethany is NOT in the set.
你可能已经注意到HashSet元素显示的顺序与它们添加到集合中的顺序不匹配。集合没有元素的内部位置概念,也不会跟踪插入的顺序。当您遍历一个HashSet时,不应该期望迭代器按照它们插入的顺序返回元素。
如果您需要按照插入的顺序访问集合元素,则可以使用LinkedHashSet类而不是HashSet。LinkedHashSet类是HashSet的扩展,它保持一个内部链接列表,引用按照它们被插入的顺序排列的集合元素。这使您能够按照插入的顺序遍历集合。
tree set会按大小顺序存放element。而这里的大小顺序指的是 “1小于2” 或 实现了Comparable interface的对象。
如果我们使用TreeSet的无参数构造器,TreeSet会自动进行排序。
当然,对于没有实现Comparable interface的对象,如果我们可以修改这个对象的代码,那么我们可以让这个对象实现Comparable interface;如果我们不能修改这个对象本身,我们也可以让这个对象实现Comparator interface ,也就是创建一个Comparator,Comparator接受两个参数,然后进行比较。
Comparator interface:
对于之前的car例子,如果我们希望按照vin顺序来排序车辆:
import java.util.Comparator;
public class CarComparator implements Comparator {
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 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构造器: