1. set 接口节本介绍
**
-
无序 (添加和取出的顺序不一致),取出的顺序是固定的,没有索引
-
不允许重复元素/对象,所以做多包含一个null
-
JDK API中Set接口的实现类有
-
底层是[数组+链表+红黑树]来实现的,类似下图的结构
-
遍历用迭代和增强for循环
2. hashSet 的底层
Set hashSet = new HashSet();
- hashSet底层构造函数中 new 了一个HashMap();
public HashSet(){
map = new HashMap<>();
}
- 添加一个元素时,先得到hash值, 会转成 -> 索引
- 找到存储数据表table,看这个索引位置是否已经存放的有元素(hash值+equals)
- 如果没有,直接加入
- 如果有,调用equals 比较,如果相同,就放弃添加,如果不同,则添加到最后
- 在java8 中,如果一条链表的元素元素个数到达 TREEIFY_THRESHOLD (默认8),并且table的大小>= MIN_TREEIFY_CAPACITYMIN(默认64)就会进行树化(红黑树)
- 代码分析(以下代码为例)
Set hashSet = new HashSet();
hashSet.add("java");
hashSet.add("php");
hashSet.add("java");
System.out.println("set="+hashSet);
-
- 执行 HashSet()
public HashSet(){
map = new HashMap<>();
}
- 2.执行hashSet.add("java)
public boolean add(E e){ // e:"java"
return map.put(e,PRESENT) == null; // map:"{}" e:"java"
}
注意 put方法中PRESENT 源代码为
private static final Object PRESENT = new Object()
实际上没有任何意义,起到一个占位的作用
-
3.执行 put()方法
该方法会执行hash(key) 得到key对应的hash值 算法 h = (h = key.hashCode()) ^ (h >>> 16)
public V put(K key V value) { // key:"java" value:Object@550 此时的value 为 PRESENT
return putVal(hash(key) , key,value,false ,true);
}
- 4.执行hash(key)方法
static final int hash(Object key){ //key:"java"
int h;
return (key == null) ? 0: (h = key.hashCode()) ^ (h >>> 16); //key:"java"
}
- 5.执行putVal()方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//定义了辅助变量
Node<K,V>[] tab; Node<K,V> p; int n, i;
//table 是HashMap的一个数组, 数组中存放的Node[]
//if 语句表示 如果当前 table 是 null 或者 大小=0
// 就是第一次扩容,扩容到16个空间
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//根据key,得到hash 然后去计算该key应该存放到table表的那个索引位置
//并把这个位置的对象,赋给 P
//判断p 是否为null
//如果p为null,表示还没有存放元素,就创建一个Node (key="java",value=PRESENT)
//放在该位置 tab[i] = newNode(hash, key, value, null);
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//一个开发技巧提示:在需要辅助变量或局部变量的时候,在创建
Node<K,V> e; K k;
//如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
//并且满足下面两个条件之一:
//准备加入的key 和 p指向的Node节点的key 是同一个对象
//p指向的Node节点的key的equlas() 和 准备加入的key 比较后相同
//就不能加入
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//在判断 p 是不是 是不是一颗红黑树
//如果是一颗红黑树.就调用putTreeVal ,来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果table对应索引位置,已经是一个链表,就使用for循环比较
// 一次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后
//注意 在把元素添加到链表后, 立即判断 该链表是否已经达到8个节点, 就调用treeifyBin() 对当前这个链表进行
//进行树化(转成红黑树)
//注意 在转成红黑树时,要进行判断,如果该table数组的大小<64
//判断条件[if(tab == null) || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize();]
//如果上面条件成立时,先table数组扩容
//只有上面条件不成立时,才进行转成红黑树
// 一次和该链表的每一个元素比较过程中,如果有相同情况,就直接break
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
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;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}