TreeMap
put
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
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();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
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<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
fixAfterInsertion
//平衡红黑树
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;
while (x != null && x != root && x.parent.color == RED) {
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else {
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
putAll
public void putAll(Map<? extends K, ? extends V> map) {
int mapSize = map.size();
if (size==0 && mapSize!=0 && map instanceof SortedMap) {
Comparator<?> c = ((SortedMap<?,?>)map).comparator();
if (c == comparator || (c != null && c.equals(comparator))) {
++modCount;
try {
buildFromSorted(mapSize, map.entrySet().iterator(),
null, null);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
return;
}
}
super.putAll(map);
}
buildFromSorted
//将一个有序数据源转化为红黑树
private void buildFromSorted(int size, Iterator<?> it,
java.io.ObjectInputStream str,
V defaultVal)
throws java.io.IOException, ClassNotFoundException {
this.size = size;
root = buildFromSorted(0, 0, size-1, computeRedLevel(size),
it, str, defaultVal);
}
/**
* level: 当前树层级,初始调用时为0。
lo: 当前子树要处理的元素起始索引(实际是顺序处理,非真实索引)。
hi: 当前子树要处理的元素结束索引。
redLevel: 应该将节点标记为红色的层级(在红黑树中用于保持树的平衡)。
it: 用于遍历元素的迭代器(如果非空)。
str: 用于从对象流中读取元素的输入流(如果迭代器为空)。
defaultVal: 当迭代器仅提供键时使用的默认值。
*/
private final Entry<K,V> buildFromSorted(int level, int lo, int hi,int redLevel,Iterator<?> it,java.io.ObjectInputStream str,V defaultVal)
throws java.io.IOException, ClassNotFoundException {
//如果hi < lo,说明没有元素需要处理,返回null
if (hi < lo) return null;
//找到中间元素,以此为中心建树
int mid = (lo + hi) >>> 1;
Entry<K,V> left = null;
if (lo < mid)
//左子结点
left = buildFromSorted(level+1, lo, mid - 1, redLevel,
it, str, defaultVal);
// extract key and/or value from iterator or stream
K key;
V value;
//如果迭代器不为空
if (it != null) {
//默认值为空,提供键和真实值
if (defaultVal==null) {
Map.Entry<?,?> entry = (Map.Entry<?,?>)it.next();
key = (K)entry.getKey();
value = (V)entry.getValue();
// 不为空,值设为默认值
} else {
key = (K)it.next();
value = defaultVal;
}
//迭代器为空
} else { // use stream
key = (K) str.readObject();
value = (defaultVal != null ? defaultVal : (V) str.readObject());
}
//构建中间值
Entry<K,V> middle = new Entry<>(key, value, null);
// 如果当前层级level等于redLevel,则将中间节点标记为红色
if (level == redLevel)
middle.color = RED;
if (left != null) {
middle.left = left;
left.parent = middle;
}
//构建右子节点
if (mid < hi) {
Entry<K,V> right = buildFromSorted(level+1, mid+1, hi, redLevel,
it, str, defaultVal);
middle.right = right;
right.parent = middle;
}
return middle;
}
getCeilingEntry/getFloorEntry
//getCeilingEntry 查找不小于给定键的最小键的条目,包括等于给定键的情况。
final Entry<K,V> getCeilingEntry(K key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
if (cmp < 0) {
if (p.left != null)
p = p.left;
else
return p;
} else if (cmp > 0) {
if (p.right != null) {
p = p.right;
} else {
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.right) {
ch = parent;
parent = parent.parent;
}
return parent;
}
} else
return p;
}
return null;
}
final Entry<K,V> getFloorEntry(K key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
if (cmp > 0) {
if (p.right != null)
p = p.right;
else
return p;
} else if (cmp < 0) {
if (p.left != null) {
p = p.left;
} else {
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.left) {
ch = parent;
parent = parent.parent;
}
return parent;
}
} else
return p;
}
return null;
}
//getHigherEntry 查找大于给定键的最小键的条目,不包括等于给定键的情况
final Entry<K,V> getHigherEntry(K key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
if (cmp < 0) {
if (p.left != null)
p = p.left;
else
return p;
} else {
if (p.right != null) {
p = p.right;
} else {
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.right) {
ch = parent;
parent = parent.parent;
}
return parent;
}
}
}
return null;
}
final Entry<K,V> getLowerEntry(K key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
if (cmp > 0) {
if (p.right != null)
p = p.right;
else
return p;
} else {
if (p.left != null) {
p = p.left;
} else {
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.left) {
ch = parent;
parent = parent.parent;
}
return parent;
}
}
}
return null;
}
TreeSet
TreeSet基于TreeMap
public TreeSet() {
this(new TreeMap<E,Object>());
}
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
add/remove
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return m.remove(o)==PRESENT;
}
LinkedHashMap
LinkedHashMap中的构造器使用了HashMap的构造器
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
/**
* Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
* with the specified initial capacity and a default load factor (0.75).
*
* @param initialCapacity the initial capacity
* @throws IllegalArgumentException if the initial capacity is negative
*/
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
/**
* Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
* with the default initial capacity (16) and load factor (0.75).
*/
public LinkedHashMap() {
super();
accessOrder = false;
}
/**
* Constructs an insertion-ordered <tt>LinkedHashMap</tt> instance with
* the same mappings as the specified map. The <tt>LinkedHashMap</tt>
* instance is created with a default load factor (0.75) and an initial
* capacity sufficient to hold the mappings in the specified map.
*
* @param m the map whose mappings are to be placed in this map
* @throws NullPointerException if the specified map is null
*/
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
/**
* Constructs an empty <tt>LinkedHashMap</tt> instance with the
* specified initial capacity, load factor and ordering mode.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @param accessOrder the ordering mode - <tt>true</tt> for
* access-order, <tt>false</tt> for insertion-order
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
//这个变量是一个布尔值,用来指示LinkedHashMap应该使用哪种顺序来迭代其元素。如果accessOrder为true,则元素按照它们被访问的顺序来迭代(最近访问的元素首先被迭代),这称为访问顺序。如果accessOrder为false,则元素按照它们被插入的顺序来迭代,这称为插入顺序。这个属性是在LinkedHashMap对象被创建时确定的,之后不会改变。默认都是插入顺序
final boolean accessOrder;
put方法直接使用的HashMap中的put方法,但在LinkedHashMap类中,重写了afterNodeAccess(e)和 afterNodeInsertion(evict)方法,解决了LinkedHashMap中的after、before节点的问题
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
直接使用的是HashMap中的put方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果数组为空,进行resize()初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(n - 1) & hash计算出该键值对应该放在哪个桶里,获取数组的索引位置
//如果计算的位置Node不存在,直接创建节点插入
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//如果已经存在的key与传入的Key一模一样,进行覆盖
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果 index 位置元素已经存在,且是红黑树
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//如果是链表的情况
else {
//对链表进行遍历,统计链表长度
for (int binCount = 0; ; ++binCount) {
//如果节点链表的next为空
if ((e = p.next) == null) {
//找到节点链表中next为空的节点,创建新的节点插入
p.next = newNode(hash, key, value, null);
//如果插入后,节点的链表中的个数超过8,转化为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//树化
treeifyBin(tab, hash);
break;
}
//判断节点链表中的key和传入的key是否一样
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
//如果一样,退出
break;
p = e;
}
}
//如果存在相同key的节点e不为空
if (e != null) { // existing mapping for key
V oldValue = e.value;
//onlyIfAbsent表示是否在oldValue为null 的情况下更新键值对的值
if (!onlyIfAbsent || oldValue == null)
//设置新的值
e.value = value;
afterNodeAccess(e);
//返回旧的结果
return oldValue;
}
}
++modCount;
//当前大小大于临界大小、扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
HashSet
public HashSet() {
map = new HashMap<>();
}
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
public Iterator<E> iterator() {
return map.keySet().iterator();
}
public int size() {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
HashMap与HashSet的不同
1.接口和实现
-
HashSet实现了Set接口,他是一个不包含重复元素的集合
-
HashMap实现了Map接口,它是一个存储键值对的集合,每个键都映射到最多一个值
2.存储内容
-
HashSet存储的是对象本身,即它存储的是一个元素的集合,这些元素是唯一的。
-
HashMap存储的是键值对(Key-Value Pair),键是唯一的,但值可以重复。
3.用途
-
HashSet主要用于需要快速查找、插入和删除元素,且元素唯一性要求较高的场景。
-
HashMap主要用于存储键值对,并通过键来快速访问值。
4.内部实现细节
-
尽管
HashSet
内部使用HashMap
来存储元素,但它只使用了HashMap
的键部分,而忽略了值部分。在HashSet
中,所有的值都被设置为一个固定的哑元素(PRESENT
),这个元素在HashSet
的实现中没有任何实际意义,只是用来填充HashMap
的值部分。 -
HashMap则完整地使用了其键值对结构,允许用户存储和访问键值对。
HashTable
//它是一个32位整数,其中最高位(即符号位)是0,其余31位都是1。
//hash & 0x7FFFFFFF相当于取模
int index = (hash & 0x7FFFFFFF) % tab.length;
该类中的方法常用synchronized来修饰,线程安全
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
public Hashtable() {
this(11, 0.75f);
}
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
put方法(头插法)
public synchronized V put(K key, V value) {
//如果value、key为空的话,会抛出异常
if (value == null) {
throw new NullPointerException();
}
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
//抑制因泛型擦除而引起的类型转换警告
@SuppressWarnings("unchecked")
//先找到对应的桶
Entry<K,V> entry = (Entry<K,V>)tab[index];
//进行遍历,寻找是否有与添加的元素键值相同的,如果相同,覆盖掉原来的值,返回原来的值
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
//将添加的元素放在最前面
addEntry(hash, key, value, index);
return null;
}
addEntry
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) {
// 先扩容,后插入
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
//头插法
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
rehash方法
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// 扩容至原来的两倍再加1
int newCapacity = (oldCapacity << 1) + 1;
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
//创建一个新容量数组
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
//修改次数+1
modCount++;
//扩容阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
//将table指向新创建的数组
table = newMap;
//倒着遍历,只是一种可能的优化手段。在某些哈希表的实现中,特别是当哈希表的加载因子较高时,后插入的元素更有可能导致哈希冲突,并且这些元素更有可能位于数组的末尾。因此,从后往前遍历可以减少在重新哈希过程中对新插入元素的移动。
for (int i = oldCapacity ; i-- > 0 ;) {
//遍历当前桶内的元素
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
//重新获取索引值
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
// 将e插入到新链表的头部
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
remove方法
public synchronized V remove(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
//进行查找
for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
modCount++;
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
V oldValue = e.value;
//虽然e已经从链表中移除,但它仍然是一个有效的对象,在Java的内存中占用空间。如果不将e.value设置为null,那么即使这个键值对不再被HashMap管理,其值对象仍然会被垃圾回收器视为可达(因为e对象本身仍然存在),从而可能导致内存泄漏。将e.value设置为null有助于垃圾回收器识别这个值对象不再被使用,进而可以在未来的某个时间点回收它占用的内存。
e.value = null;
return oldValue;
}
}
return null;
}
HashMap和HashTable的区别
-
HashMap非线程安全,适合单线程环境,而HashTable是线程安全但效率低,推荐使用ConcurrentHashMap替代。
-
HashTable不允许空键值,HashMap则可以
-
在结构上,hashTable一直是数组和链表结构,而hashMap在1.8时改为了数组和链表及红黑树
-
在遍历上,HashTable使用Enumeration,HashMap使用Iterator。
-
在容量上,hashTable默认为11,扩容为2n+1,而hashMap默认为16,扩容为2倍。如添加指定长度,table会直接使用,而map总会扩充为2的n次幂。
-
在散列算法上,上HashTable会使用key对hashCode对长度取模,hashMap会做一些扰动来达到更好的分布