Set接口
Set集合继承自Collection集合
类似数学中的集合概念;特点:集合中元素不能重复,且元素无序排列
无序排列理解:元素所在的位置与向集合中添加元素的顺序没有关系
元素不重复原因:底层数据结构是一个哈希表,能保证元素是唯一的,元素不重复!!!
它通过它的接口子实现类HashSet集合去实例化,而HashSet集合底层是HashMap集合的实例!!!
实例1
package org.westos_01;
import java.util.HashSet;
import java.util.Set;
public class SetDemo {
/**
* 说明问题:
* 1)Set集合元素不重复(从打印的内容以及集合元素个数)
* 2)集合元素无序(添加的顺序与打印的顺序)
* 3)添加、增、删、查询方法的使用---这四个方法的返回值类型都是boolean类型
* 4)Set集合存储字符串元素并遍历---for增强循环(jdk5后的版本)
*/
public static void main(String[] args) {
//创建Set集合对象
Set<String> set = new HashSet<String>() ;
//添加元素(增)
set.add("hello");
set.add("java") ;
set.add("java") ;
set.add("world") ;
set.add("world") ;
set.add("world") ;
System.out.println("集合中添加的元素:"+set);//打印的是内容(原因:传入参数类型是String,底层重写了toString()方法)
System.out.println("集合中元素的个数:"+set.size());
//增强for遍历
for(String s :set){
System.out.println(s);
}
//删除元素(删)
System.out.println("是否成功删除:"+set.remove("hello"));//删除是否成功
System.out.println("删除后集合中的元素:"+set);
//查询
System.out.println("是否包含此元素:"+set.contains("hello"));
}
}
HashSet类
描述:按照哈希算法来存取集合的元素,此类实现Set 接口,由哈希表(实际上是一个 HashMap 实例)支持
特点:它不保证 Set 的迭代顺序;元素唯一(不重复)
注:HashSet有一个子类:LinkedHashSet类(下面会提到)
存取和查找的机理
机理:当向集合中添加一个元素时,HashSet会调用对象的hashCode()方法获取哈希码,然后根据这个哈希码进一步计算出对象在集合中存放的位置。
问题1 为什么能保证HashSet集合元素唯一?
当创建一个HashSet集合的对象时,会先调用其构造方法(蓝框标记)看源码1
即:
源码说明:可以看到实际上是创建了一个HashMap类对象的实例,map是HashMap类对象的实例
此时add()添加元素,看对应的源码2
源码说明:其实是调用了HashMap的put方法
看HashMap的put()方法的源码3
源码说明:调用了HashMap中的hash()方法和putVal()方法
看hash方法(重要)的源码4
源码说明:携带hashSet集合元素key,调用对象(传入参数类型)的hashCode()方法,进行位运算;涉及到Object类hashCode()方法重写
看putVal()方法(具体用处不知,后续会提到)源码5
//HashMap类中的putVal方法
public class Test1 {
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K, V>[] tab;
Node<K, V> p;
int n, i;
//1---首先判断hash表是否是空的,如果空,则resize扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//2---通过key计算得到hash表下标,如果下标处为null,就新建链表头结点,在方法最后插入即可
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//3---如果下标处已经存在节点,则进入到这里
else {
Node<K, V> e;
K k;
//4---先看hash表该处的头结点是否和key一样(hashcode和equals比较),一样就更新
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//5---hash表头结点和key不一样,则判断节点是不是红黑树,是红黑树就按照红黑树(jdk8的特性)处理
else if (p instanceof TreeNode)//TreeNode(后续会提到)
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
//6---如果不是红黑树,则按照之前的hashmap原理处理
else {
//1)遍历链表
for (int binCount = 0;; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//2)显然当链表长度大于等于7的时候,也就是说大于8(由于插入一个元素)的话,
//就转化为红黑树结构,针对红黑树进行插入(logn复杂度)
if (binCount >= TREEIFY_THRESHOLD - 1)//TREEIFY_THRESHOLD常量字段为8
treeifyBin(tab, hash);//treeifyBin(后续会提到)
break;
}
//3)如果hash码相同,则调用相应的集合元素(元素参数类型)的equals()方法判断,涉及到重写
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;//如果没有找到该key(元素)的结点,则执行插入操作,需要对modCount增1。
if (++size > threshold) // 在执行插入操作之后,如果size大于threshold容量,要扩容
resize();
afterNodeInsertion(evict);
return null;
}
}
源码说明:调用了元素类型的equals()方法,涉及equals()方法的重写
源码6----table 哈希表
源码说明:当new HashMap实例时,并没有初始化其成员变量table(也即并没有为table分配内存)。只有当put元素时才通过resize方法对table进行初始化
源码7----resize()方法(随后补充)
总结:如果自定义类型,必须重写hashCode()和equals()方法------快捷键生成---ctrl+alt+s+h
对问题1的解答
HashSet集合的add()方法,底层是依赖于双列集合HashMap<K,V>的put(K key,V value)来实现的,而put(K key,V value)方法底层的实现又依赖于HashCode()和equals()方法,传递添加元素的时候,首先判断的是每一个元素对应的HashCode值是否一样,如果HashCode值一样,还比较他们的equals()方法,由于现在集合存储的是String类型,String类型本身重写了equals()方法,所以,默认比较的是内容是否相同,如果内容相同,这里最终返回的就是第一次存储的那个元素,由这两个方法保证元素唯一性!
相关链接:1 点击打开链接(比较好),2 点击打开链接,3 点击打开链接,4 点击打开链接,5 点击打开链接,6 点击打开链接
HashSet的子实现类LinkedHashSet
特点:具有可预知迭代顺序的Set接口的哈希表和链接列表实现。
原因:
1 由哈希表保证元素的唯一性
2 由链接列表来保证元素的有序性(保存插入时的顺序)
实例2
package org.westos_03;
import java.util.LinkedHashSet;
public class LinkedHashSetDemo {
public static void main(String[] args) {
//创建LinkedHashSet集合对象
LinkedHashSet<String> link = new LinkedHashSet<String>();
//给集合中添加元素
link.add("hello") ;
link.add("world") ;
link.add("world") ;
link.add("Java") ;
link.add("Java") ;
link.add("JavaWeb") ;
link.add("JavaWeb") ;
//遍历集合
for(String s: link){
System.out.println(s);
}
}
}