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='易烊千玺'}]
本文详细介绍了Java中的HashSet集合,强调其不允许存储重复元素和无序的特点。HashSet基于哈希表实现,哈希表是数组+链表(JDK1.8后增加红黑树)的结构。哈希表通过对象的hashCode()方法确定存储位置,避免冲突。当哈希值相同时,使用equals()方法判断元素是否相同。为了在HashSet中确保对象唯一,需要重写对象的equals()和hashCode()方法。文章通过示例展示了如何存储自定义类型元素,并讨论了存储过程中可能出现的哈希碰撞问题。
614

被折叠的 条评论
为什么被折叠?



