一、前言
在 Java 集合(一) 中我们已经讲了 Collection 集合接口、Iterator 迭代器和泛型,今天我们来讲 Set 集合、List 集合 和 Collections 工具类。
二、Set 集合
Set 接口继承自 Collection 接口,它与 Collection 接口中的方法基本一致,并没有对 Collection 接口进行功能上的扩展,只是比 Collection 接口更加严格了,与 List 集合不同的是,Set 集合不允许存储重复的元素,而且 Set 集合是没有索引的。
Set 集合有多个子类,这里我们介绍其中的 HashSet 与 LinkedHashSet 这两个集合。
2.1、HashSet 集合
HashSet 集合实现了 Set 接口,首先 Set 集合有的特点它都有,同时它还有以下特点:
- 是一个无序的集合,存储元素和取出元素的顺序有可能不一致。
- 底层是一个哈希表结构,查询的速度非常的快。
HashSet 集合代码演示如下所示:
public class SetDemo01 {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
// 使用 add() 方法添加元素
set.add(1);
set.add(3);
set.add(2);
set.add(1);
// 使用迭代器遍历集合
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 使用增强 for 循环遍历
for (Integer integer : set) {
System.out.println(integer);
}
}
}
2.2、哈希值
哈希值是一个十进制的整数,由系统随机给出,实际上就是对象的地址值,是一个逻辑地址,是模拟出来的地址,不是数据实际存储的物理地址。在 Object 类有一个方法 hashCode(),可以获取对象的哈希值。
hashCode() 方法源码如下:(native 代表该方法调用的是本地操作系统的方法)
public native int hashCode();
toString() 的源码如下:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
可以看出 toString() 也调用了 hashCode() 并将其转化为十六进制。
哈希值代码演示如下所示:
public class Person extends Object {
public static void main(String[] args) {
Person p1 = new Person();
int h1 = p1.hashCode();
System.out.println(h1); // 1163157884
Person p2 = new Person();
int h2 = p2.hashCode();
System.out.println(h2); // 1956725890
/**
* toString() 的源码
* public String toString() {
* return getClass().getName() + "@" + Integer.toHexString(hashCode());
* }
*/
System.out.println(p1); // com.zjgsu.Set.Person@4554617c
System.out.println(p2); // com.zjgsu.Set.Person@74a14482
/**
* String 类的哈希值
* String 类重写了 hashCode() 所以下面两个哈希值是一样的
*/
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
}
2.3、哈希表
在 JDK1.8 之前哈希表 = 数组 + 链表,但是在JDK1.8之后,哈希表 = 数组 + 链表 + 红黑树(提高查询效率)。具体如下图所示:
2.4、Set 集合存储元素不重复的原理
我们先来执行以下下面的代码:
public class SetDemo02 {
public static void main(String[] args) {
HashSet<String> set = new HashSet<String> ();
String s1 = new String("abc");
String s2 = new String("abc");
set.add(s1);
set.add(s2);
set.add("重地");
set.add("通话");
set.add("abc");
System.out.println(set); // [重地, 通话, abc]
}
}
打印结果如下所示:(可以看到 “abc” 只有一个)
我们根据代码来分析一下:
- HashSet set = new HashSet ():现在我们已经知道hashSet 底层是一个哈希表,所以这句代码会创建一个哈希表。
- set.add(s1):add() 方法会调用 s1 的 hashCode() 方法。计算字符串 “abc” 的哈希值,哈希值是 96354,在集合中找有没有 96354 这个哈希值的元素,发现没有,就把 s1 存储到集合中。
- set.add(s2):add() 方法会调用 s2 的 hashCode() 方法。计算字符串 “abc” 的哈希值,哈希值是 96354,在集合中找有没有 96354 这个哈希值的元素,发现有(哈希冲突),s2 会调用 equals() 方法和哈希值相同的元素进行比较,s2.equals(s1) 返回 true,两个元素相同,就不会把 s2 存储到集合中。
- set.add(“重地”);:add() 方法会调用 “重地” 的 hashCode() 方法。计算字符串 “重地” 的哈希值,哈希值是