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='易烊千玺'}]