-
- HashSet特征
- 无序——添加和取出元素的顺序不一致;没有索引
- 元素不可以重复,所以最多包含一个null
- 取出的顺序虽然不是添加的顺序,但是固定
- 底层采用HashMap实现,所以主要介绍HashMap底层代码
- HashMap底层实现:数组+链表/红黑树
HashMap大致数据结构
- 看代码⬇️
public class test { public static void main(String[] args) { //创建一个HashSet对象 HashSet set = new HashSet(); for (int i = 1 ;i <= 20; i++){ //添加元素 set.add(i); } System.out.println(set); } }
- HashSet set =newHashSet();
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { public HashSet() { //创建一个HashMap对象 map = new HashMap<>(); } }
看到没有!HashMap,接着往下看
2. map = new HashMap<>();
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { //指定数组的初始大小为16. 1<<4=1*2*2*2*2 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 //构造函数 public HashMap() { /*给loadFactor赋值 0.75 用来计算数组的临界点下标 */ this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } }
3. set.add(i);
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { //添加一个元素 返回boolean类型表示成功或失败 public boolean add(E e) { //PRESENT——表示map<K,V>中的value,这里为一个Object对象,K=e——在键中存储要添加的元素 return map.put(e, PRESENT)==null; } }
4. map.put(e, PRESENT)
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { public V put(K key, V value) { //hash(key)用来计算hash值,最终得到将要存储元素的下标 //key,value即Map的键值对 return putVal(hash(key), key, value, false, true); } }
5. putVal(hash(key), key, value,false,true);
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { 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 = Map结构中的数组初始为空 if ((tab = table) == null || (n = tab.length) == 0) //一开始初始化准备添加第一个元素,上面条件成立,先看下resize代码 -> 6.1 n = (tab = resize()).length; //如果通过hash算法得到的下标没有存储任何元素,则将元素通过Node存放在当前下标 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; /*判断如果当前要存储hash值与计算出的hash值相等并且将要存储的元素的地址或内容与 原下标的地址或内容相等则执行 */ if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //否则如果当前下标存储结构为红黑树 之行下列代码块 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { //hash值相同,其他上述条件不成立时.循环数组当前下标的链表中的元素 for (int binCount = 0; ; ++binCount) { //之所以要判断链表下一节点是否指向空,是因为要将新的节点添加到链表尾部 if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); /*判断如果当前下标的链表中节点的数量>=7(从0开始数),则考虑将链表 转为红黑树 */ 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; } } //如果e不是null,返回的也不是null 表示添加失败 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); //返回null 表示添加成功 return null; }
}
6.1. resize()
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; //oldCap=0,因为数组现在为null int oldCap = (oldTab == null) ? 0 : oldTab.length; //oldThr=0,默认为0 —数组长度临界值old int oldThr = threshold; int newCap, newThr = 0; //不成立,跳过 if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } //不成立,跳过 else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { //程序来到这 //得到默认为数组指定的大小 16 newCap = DEFAULT_INITIAL_CAPACITY; //得到默认为数组指定的临界点下标 16 * 0.75 = 12 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } //不成立,跳过 if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) //创建一个长度为16的Node类型数组——Node对象可形成链表 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //将创建的数组赋予Map中的table数组 table = newTab; /*...省略代码段...*/ return newTab; } }
补充:
当链表的节点到8个并且数组的长度>=64,链表会转为红黑树;
如果节点>8,但是数组的长度<64,则先对数组进行扩容,达到64时,会重新计算hash值并将数据结构转为红黑树;
Map中存储的元素在达到临界值的时候,会自动*2扩容;这里的元素指Map中元素的总个数,不单单是数组中的元素,也包括链表中的元素。
END