HashSet源码
package com.dream.enum_util_class;
import java.util.EnumMap;
import java.util.Map.Entry;
import java.util.Set;
public class Test02 {
public static void main(String[] args) {
/**
*
* EnumMap工具类
*
* 将枚举类中对象放入Map集合中
*/
EnumMap<Signal, String> map = new EnumMap<>(Signal.class);
map.put(Signal.RED, "红灯");
map.put(Signal.YELLOW, "黄灯");
map.put(Signal.GREEN, "绿灯");
Set<Entry<Signal,String>> entrySet = map.entrySet();
for (Entry<Signal, String> entry : entrySet) {
Signal key = entry.getKey();
String value = entry.getValue();
System.out.println(key + " -- " + value);
}
}
}
理解HashSet底层由HashMap实现
HashMap源码
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>{
//默认长度
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//16
//默认负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//最大长度
static final int MAXIMUM_CAPACITY = 1 << 30;
//负载因子
final float loadFactor;//0.75
//阈值(容量*负载因子)
int threshold;//12
//空实例的数组
static final Entry<?,?>[] EMPTY_TABLE = {};
//存放数据的容器
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//new Entry[16]
//hash掩码/hash随机值
transient int hashSeed = 0;
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
//initialCapacity = 16
//loadFactor = 0.75
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))//NAN:not a Number
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}
public V put(K key, V value) {
//第一次添加元素时进入的判断
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//判断key是否为null
if (key == null)
return putForNullKey(value);
//获取到key的hash值
int hash = hash(key);
//通过hash值获取数组中的下标
int i = indexFor(hash, table.length);
//e = 杨晨的Entry
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
//如果key相同,就替换value值,并返回老的value
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;//16
//当容量 == 最大容量时
if (oldCapacity == MAXIMUM_CAPACITY) {
//阈值 = Integer.MAX_VALUE
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];//new Entry[32];
//数据的移位:把原来数组中的数据迁移到新数组中
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
//计算阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
//判断是否扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);//头插法
size++;
}
static int indexFor(int h, int length) {
return h & (length-1);
}
final int hash(Object k) {
int h = hashSeed;
//如果key是String类型,则计算hash值的过程中使用到hashSeed
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
private void inflateTable(int toSize) {//16
// 传入的数字经过计算,都会或得到2的幂
int capacity = roundUpToPowerOf2(toSize);//capacity = 16
//threshold = 12
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
private static int roundUpToPowerOf2(int number) {//16
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
//获取的hash掩码
final boolean initHashSeedAsNeeded(int capacity) {
boolean currentAltHashing = hashSeed != 0;
boolean useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean switching = currentAltHashing ^ useAltHashing;
if (switching) {
hashSeed = useAltHashing
? sun.misc.Hashing.randomHashSeed(this)
: 0;
}
return switching;
}
//映射关系类/节点类
static class Entry<K,V> implements Map.Entry<K,V> {
final K key; ----- key
V value; --------- value
Entry<K,V> next; - 下一个节点的地址
int hash; -------- key的hash值
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
}
}
-
HashMap的数组长度为什么是2的幂?
数组长度2的幂会使元素在数组中分布的更加均匀
因为如果不是2的幂,length-1的二进制有可能某一位出现0,就会导致数组中某个下标上永远存不到元素,就会使得空间的浪费
-
HashMap的扩容机制/扩容倍数?
HashMap扩容的长度是原来的2倍
-
HashMap默认负载因子是多少?
0.75
-
为什么HashMap默认负载因子是0.75
取得了时间和空间的平衡
负载因子过小:存储元素个数较少的时候就扩容,浪费空间
负载因子过大:存储元素个数较多的时候就扩容,浪费时间
-
HashMap的数组最大长度是多少?
1<<30
-
HashMap的数组最大长度为什么是1<<30?
HashMap数组长度必须是2的幂,并且数组长度的类型是int
所以1<<30是int类型取值范围内能取到最大的2的幂
-
什么是HashMap的hash表
就是数组
-
什么是hash碰撞/冲突
准元素在数组中的下标上有元素,这就是hash碰撞
-
什么是hash桶
Hash数组中的Entry,因为Entry中有个叫做next的属性,Entry就是一个单向列表
-
HashMap存放null键的位置在哪?
在数组中下标为0的位置
-
HashMap扩容出现的问题/Hash回环/Hash死循环
在扩容的情况下,Hash桶中Entry的next指向下一个Entry的地址出现重复
一般Hash回环出现在多线程扩容下,出现这个问题都是程序员的责任,因为HashMap明确说明该集合是线程不安全,在多线程的情况下应该使用ConcurrentHashMap
-
JDK1.8版本对于HashMap进行了哪些改动?
hash()进行了改进:将hashCode值认为有高16位和低16位,并且将高16位 ^ 低16位,让hash值分布更加均匀
JDK1.7:数组+链表的形式,头插法
JDK1.8:数组+链表+红黑树,尾插法
- JDK1.8版本HashMap为什么有黑红数的改进?
加入红黑树使得查询更有效率
- JDK1.8版本HashMap什么时候从链表转换为红黑树?
数组长度大于64,并且链表长度大于8
- JDK1.8版本HashMap为什么链表大于8就转换为红黑树?
因为泊松分布额统计概率学,统计出链表大于8的情况以及是及其微小了
经验:要防止hash碰撞,一定也要重写hashCode()
TreeSet源码
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>{
private transient NavigableMap<E,Object> m;
private static final Object PRESENT = new Object();
public TreeSet() {
this(new TreeMap<E,Object>());
}
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
}
TreeSet底层由TreeMap实现
TreeMap源码
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>{
private final Comparator<? super K> comparator;
private transient Entry<K,V> root = null;
public TreeMap() {
comparator = null;
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
public V put(K key, V value) {
//t = 麻生希的Entry
Entry<K,V> t = root;
if (t == null) {
compare(key, key); //目的:1.检查Key是否为null 2.检查该类是否实现内置比较器
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
//比较结果
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator;
if (cpr != null) {//外置比较器的逻辑
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}else {//内置比较器的逻辑
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
do {
//椎名空的Entry
parent = t;
//4
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
//北岛玲的Entry
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
//红黑树的制衡
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left = null;
Entry<K,V> right = null;
Entry<K,V> parent;
boolean color = BLACK;
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
}
}
外置比较器优先于 内置比较器
https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
《算法导论》中对于红黑树的定义如下:
- 每个结点或是红的,或是黑的
- 根节点是黑的
- 每个叶结点是黑的
- 如果一个结点是红的,则它的两个儿子都是黑的
- 对每个结点,从该结点到其子孙节点的所有路径上包含相同数目的黑结点