文章目录
一、基本介绍
1.定义
set接口是集合类接口collection的子接口,其拥有collection的字段和方法,也有自己的特性。set为单列集合,常用方法与collection一样,如add、remove等方法。其常用实现子类有HashSet、LinkedHashSet。
2.特点
1)无序
添加和取出的顺序不一致,没有索引。
2)元素唯一
不允许出现重复元素,所以最多包含一个null。
3)遍历方式
可以使用迭代器和增强for循环,但不能使用普通for循环 。
代码说明:
public class HashSet_ {
public static void main(String[] args) {
//1.HashSet底层是实现HashMap()
//public HashSet() {
// map = new HashMap<>();
// }
HashSet set = new HashSet();
//2.HashSet只能有一个null
//3.HashSet不保证有序,取决于hash后,再确定索引的值
set.add(null);set.add("rookie");set.add("shy");
set.add("jk");set.add("ning");set.add(null);
Iterator iterator = set.iterator();
System.out.println("set=" + set);
while (iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
}
}
运行结果:
set=[null, jk, shy, ning, rookie]
null
jk
shy
ning
rookie
二、实现类
1.HashSet(重点)
1)介绍
set拥有的特性HashSet都拥有
2)add(E)机制
a.HashSet底层是HashMap(即数组+链表+红黑树)。
b.添加一个元素时,先通过算法得到hash值,再转化为索引值。
c.找到数据存储表table,看这个索引之前是否存放的有数据。
d.如果没有,直接加入。
e.如果有,调用equals比较,如果相同,就放弃添加;如果不相同就添加在最后。
f.在java8中,如果一条链表的元素个数达到8个,而且table(即数据数组)的大小达到64就会进行树化(红黑树)
代码说明(包括源码解读):
public class HashSetSource {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("java");
hashSet.add("c++");
hashSet.add("java");
System.out.println("hashSet=" + hashSet);
<font color=red>
/*源码分析(主要搞懂add方法的底层原理)
1.创建对象(HashSet的底层就是HashMap)
public HashSet() {
map = new HashMap<>();
}
2.第一次add的过程
即调用put方法,若返回true则添加成功,反之失败
public boolean add(E e) { e = "java"
return map.put(e, PRESENT)==null;
}
public V put(K key, V value) { (再次验证底层是HashMap,即操作的是键值对)
return putVal(hash(key), key, value, false, true);
}
1)利用key的hashcode通过"(h = key.hashCode()) ^ (h >>> 16)"算法得到hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2)执行putVal方法(重点)
2)a 第一次扩容到 16空间
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
resize()的部分代码:
newCap为数据数组分配的元素大小,newThr为临界值,若超过newThr就扩容
newCap = DEFAULT_INITIAL_CAPACITY;
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
threshold = newThr;
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (++size > threshold)
resize();
2)b 根据前面得到的hash值 去计算key应该存放到table的哪一个索引位置
并将该位置所在的对象赋给p,再判断p是否为空,如果为空,则在该位置
创建一个新的node(key,value)
if ((p = tab[i = (n - 1) & hash]) == null) i = 3
tab[i] = newNode(hash, key, value, null);
3.第二次add的过程
if ((p = tab[i = (n - 1) & hash]) == null) i = 2
tab[i] = newNode(hash, key, value, null);
4.第三次add的过程(加相同对象)
因为相同对象的hash值相同,所以执行else里的代码
else {
Node<K,V> e; K k;
a .如果p指向Node节点的key和准备添加的key的hash值一样
并且满足下面两个条件之一:
1)准备加入的key与p指向Node节点的key是同一个对象
2)两者用equals比较后相同(equals方法可由程序员自行定义)
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
b .再判断是不是一颗红黑树
如果是一颗红黑树,就调用putTreeVal进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
c.如果前两个都不是 则依次遍历链表,将待添加的key与各节点依次进行如a中的
比较,直到链表结束,若其中有一个节点与之相同则break不添加,反之添加到链表的最后
注意:如果将元素添加到链表中,就立刻判断binCount是否达到7即数组长度是否达到8,若
达到则执行treeifyBin.但又由 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize(); 可知只有当数组长度达到64时才变为红黑树,否则只是扩容
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD(8) - 1) //
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
*/</font>
}
}
运行结果:
3)扩容和树化详解
代码示例:
public class HashSetIncrease {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
for(int i = 0;i < 100;i++){
hashSet.add(i+1);
}
}
}
调试验证:
数组元素小于12时:
数组元素超过12时:
代码示例:
public class HashSetIncrease {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
for(int i = 0;i < 100;i++){
hashSet.add(new student());
}
}
}
class student{
@Override
public int hashCode() {
return 77;
}
}
通过调试该代码可验证上诉结论。
2.LinkedHashSet
1)介绍
LinkedHashSet是HashSet的子类,与HashSet特征相似,相较于HashSet其不同在于集合中的元素是有序的。
2)详述
a. LinkedHashSet底层是实现LinkedHashMap(数组+双向链表)。
b. LinkedHashSet的map都有一个head和tail,分别指向头元素和尾元素。
c.table中的每个元素都有before和after字段,分别指向前一个和后一个元素。
d.数组是HashMap
N
o
d
e
类
型
,
元
素
是
L
i
n
k
e
d
H
a
s
h
M
a
p
Node类型,元素是LinkedHashMap
Node类型,元素是LinkedHashMapEntry类型,后者是前者的子类。
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
e.LinkedHashSet add方法的底层与HashSet底层类似。