目录
框架注释:
- Map是映射接口,Map中存储的是键值对
- AbstractMap是继承于Map的抽象类,实现了Map接口中的绝大多数API,所有Map的实现类都是通过该抽象类来减少代码量的
- SortedMap 是继承于Map的接口。SortedMap中的内容是排序的键值对,排序的方法是通过比较器(Comparator)。
- NavigableMap 是继承于SortedMap的接口。相比于SortedMap,NavigableMap有一系列的导航方法;如"获取大于/等于某对象的键值对"、“获取小于/等于某对象的键值对”等等
- TreeMap 继承于AbstractMap,且实现了NavigableMap接口;因此,TreeMap中的内容是“有序的键值对”!
- TreeMap 继承于AbstractMap,且实现了NavigableMap接口;因此,TreeMap中的内容是“有序的键值对”!
- Hashtable 虽然不是继承于AbstractMap,但它继承于Dictionary(Dictionary也是键值对的接口),而且也实现Map接口;因此,Hashtable的内容也是“键值对,也不保证次序”。但和HashMap相比,Hashtable是线程安全的,而且它支持通过Enumeration去遍历。
- WeakHashMap 继承于AbstractMap。它和HashMap的键类型不同,WeakHashMap的键是“弱键”。
Map接口
public interface Map<K,V>
Map接口的实现类有两个“标准的”构造方法,第一个,void(无参数)构造方法,用于创建空映射;第二个,带有单个 Map 类型参数的构造方法,用于创建一个与其参数具有相同键-值映射关系的新映射。实际上,后一个构造方法允许用户复制任意映射,生成所需类的一个等价映射。尽管无法强制执行此建议(因为接口不能包含构造方法),但是 JDK 中所有通用的映射实现都遵从它。
Map提供接口分别用于返回 键集、值集或键-值映射关系集。
entrySet()用于返回键-值集的Set集合
keySet()用于返回键集的Set集合
values()用户返回值集的Collection集合
因为Map中不能包含重复的键;每个键最多只能映射到一个值。所以,键-值集、键集都是Set,值集时Collection。
Map提供了“键-值对”、“根据键获取值”、“删除键”、“获取容量大小”等方法。
Map.Entry接口
interface Entry<K,V>是Map的内部接口,里面有许多具体实现的方法,声明为default类型的(JDK1.8新特性:接口可以声明默认方法)。Map通过Map.entrySet来获取Map.Entry的键值对集合
AbstractMap抽象类
public abstract class AbstractMap<K,V> implements Map<K,V>
该类时Map接口的主体实现,它以最大限度减少了Map接口的工作实现。
要实现不可修改的映射,编程人员只需扩展此类并提供 entrySet 方法的实现即可,该方法将返回映射的映射关系 set 视图。通常,返回的 set 将依次在 AbstractSet 上实现。此 set 不支持 add() 或 remove() 方法,其迭代器也不支持 remove() 方法。
要实现可修改的映射,编程人员必须另外重写此类的 put 方法(否则将抛出 UnsupportedOperationException),entrySet().iterator() 返回的迭代器也必须另外实现其 remove 方法。
SortedMap接口
public interface SortedMap<K,V> extends Map<K,V>
该接口是有序的键值映射,有两种排序方式:自然排序和用户指定比较器。所有元素都必须实现Comparable接口,或者被指定的比较器接受。
另外,所有SortedMap 实现类都应该提供 4 个“标准”构造方法:
(01) void(无参数)构造方法,它创建一个空的有序映射,按照键的自然顺序进行排序。
(02) 带有一个 Comparator 类型参数的构造方法,它创建一个空的有序映射,根据指定的比较器进行排序。
(03) 带有一个 Map 类型参数的构造方法,它创建一个新的有序映射,其键-值映射关系与参数相同,按照键的自然顺序进行排序。
(04) 带有一个 SortedMap 类型参数的构造方法,它创建一个新的有序映射,其键-值映射关系和排序方法与输入的有序映射相同。无法保证强制实施此建议,因为接口不能包含构造方法。
NavigableMap接口
public interface NavigableMap<K,V> extends SortedMap<K,V> { }
它是一个可导航的键值对集合,具有为给定搜索目标提供最接近的匹配项的导航方法。
NavigableMap除了继承SortedMap的特性外,它的提供的功能可以分为4类:
第1类,提供操作键-值对的方法。
lowerEntry、floorEntry、ceilingEntry 和 higherEntry 方法,它们分别返回与小于、小于等于、大于等于、大于给定键的键关联的 Map.Entry 对象。
firstEntry、pollFirstEntry、lastEntry 和 pollLastEntry 方法,它们返回和/或移除最小和最大的映射关系(如果存在),否则返回 null。
第2类,提供操作键的方法。这个和第1类比较类似
lowerKey、floorKey、ceilingKey 和 higherKey 方法,它们分别返回与小于、小于等于、大于等于、大于给定键的键。
第3类,获取键集。
navigableKeySet、descendingKeySet分别获取正序/反序的键集。
第4类,获取键-值对的子集。
Dictionary抽象类
public abstract class Dictionary<K,V> {}
HashMap
HashMap简介
HashMap是一个散列表,存储的是键值对映射,它的实现不是同步的,意味着他不是线程安全的。此外,它的key和value都可以为null,映射也并非是有序的。
HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。
HashMap数据结构
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
jdk1.8的新特性是每个桶中元素(是一个Entry)的存储方式不再是链表,而是红黑树+链表,因此HashMap的数据结构是数组+链表+红黑树
HashMap的构造方法
// 默认构造函数。
HashMap()
// 指定“容量大小”的构造函数
HashMap(int capacity)
// 指定“容量大小”和“加载因子”的构造函数
HashMap(int capacity, float loadFactor)
// 包含“子Map”的构造函数
HashMap(Map<? extends K, ? extends V> map)
HashMap源码解析
HashMap里面的Node表示存储键值对的桶,它是HashMap里面的内部类
static class Node<K,V> implements Map.Entry<K,V>
//每个Node的内部结构
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
注意:HashMap中的Node类实现了 Map.Entry接口,实现了里面的方法
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
// 序列号
private static final long serialVersionUID = 362498820763181265L;
// 默认的初始容量是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认的加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 当桶(bucket)上的结点数大于这个值时会转成红黑树
static final int TREEIFY_THRESHOLD = 8;
// 当桶(bucket)上的结点数小于这个值时树转链表
static final int UNTREEIFY_THRESHOLD = 6;
// 桶中结构转化为红黑树对应的table的最小大小
static final int MIN_TREEIFY_CAPACITY = 64;
// 存储元素的数组,总是2的幂次倍
transient Node<k,v>[] table;
// 存放具体元素的集
transient Set<map.entry<k,v>> entrySet;
// 存放元素的个数,注意这个不等于数组的长度。
transient int size;
// 每次扩容和更改map结构的计数器
transient int modCount;
// 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容
int threshold;
// 填充因子
final float loadFactor;
}
构造方法
public HashMap(int initialCapacity, float loadFactor) {
// 初始容量不能小于0,否则报错
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
// 初始容量不能大于最大值,否则为最大值
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// 填充因子不能小于或等于0,不能为非数字
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// 初始化填充因子
this.loadFactor = loadFactor;
// 初始化threshold大小
this.threshold = tableSizeFor(initialCapacity);
}
public HashMap(int initialCapacity) {
// 调用HashMap(int, float)型构造函数
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
// 初始化填充因子
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
public HashMap(Map<? extends K, ? extends V> m) {
// 初始化填充因子
this.loadFactor = DEFAULT_LOAD_FACTOR;
// 将m中的所有元素添加至HashMap中
putMapEntries(m, false);
}
putMapEntries(Map<? extends K, ? extends V> m, boolean evict)函数将m的所有元素存入本HashMap实例中。
public V get(Object key) {//通过键来获取值
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {//HashMap中并没有提供直接的getNode方法给用户,而是通过get方法来取得值
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {//如果桶中不止一个结点
if (first instanceof TreeNode)//如果为红黑树节点,那么就在红黑树里面查找
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//否则在链表中查找
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
resize函数
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;//将当前table保存
int oldCap = (oldTab == null) ? 0 : oldTab.length;//保存当前table的大小
int oldThr = threshold;//保存当前阈值
int newCap, newThr = 0;
if (oldCap > 0) {//如果当前table的容量大于0
if (oldCap >= MAXIMUM_CAPACITY) {//如果当前table的大小大于最大容量
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如果之前阈值大于0
newCap = oldThr;//将旧的阈值设为新的容量值
else { // zero initial threshold signifies using defaults使用默认值
newCap = DEFAULT_INITIAL_CAPACITY;
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"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//初始化table
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
WeakHashMap
WeakHashMap简介
WeakHashMap继承于AbstractHashMap,实现了Map接口
与HashMap一样,WeakHashMap也是散列表,用于存储键值对,并且键和值都可以为null,不同的是WeakHashMap的键是“弱键”,即当某个键不再正常使用时,该键值对会从WeakHashMap列表中删除。对于给定的键,其映射的存在并不影响垃圾回收器对它的丢弃,这就意味着该键是可以被终止的,被终止然后被回收,如果某个键被终止了意味着其键值对已经从映射中被移除了。
大致上就是,通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。实现步骤是:
(01) 新建WeakHashMap,将“键值对”添加到WeakHashMap中。
实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
(02) 当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
(03) 当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。
这就是“弱键”如何被自动从WeakHashMap中删除的步骤了。
WeakHashMap的构造函数
// 默认构造函数。
WeakHashMap()
// 指定“容量大小”的构造函数
WeakHashMap(int capacity)
// 指定“容量大小”和“加载因子”的构造函数
WeakHashMap(int capacity, float loadFactor)
// 包含“子Map”的构造函数
WeakHashMap(Map<? extends K, ? extends V> map)
WeakHashMap的数据结构
WeakHashMap继承于AbstractMap,并且实现了Map接口。
WeakHashMap是哈希表,但是它的键是"弱键"。WeakHashMap中的几个重要的成员变量:table, size, threshold, loadFactor, modCount, queue。
table是一个Entry[]数组类型,而Entry实际上就是一个单向链表。哈希表的"key-value键值对"都是存储在Entry数组中的。
size是Hashtable的大小,它是Hashtable保存的键值对的数量。
threshold是Hashtable的阈值,用于判断是否需要调整Hashtable的容量。threshold的值="容量*加载因子"。
loadFactor就是加载因子。
modCount是用来实现fail-fast机制的
queue保存的是“已被GC清除”的“弱引用的键”。
WeakHashMap源码分析
重要成员变量
//默认初始容量为16
private static final int DEFAULT_INITIAL_CAPACITY = 16;
//规定最大容量不能超过2的30次方
private static final int MAXIMUM_CAPACITY = 1 << 30;
//默认加载因子
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 存储数据的Entry数组,长度是2的幂。
// WeakHashMap是采用拉链法实现的,每一个Entry本质上是一个单向链表
Entry<K,V>[] table;
//WeakHashMap中键值对的数量
private int size;
// WeakHashMap的阈值,用于判断是否需要调整WeakHashMap的容量(threshold = 容量*加载因子)
private int threshold;
//实际加载因子大小
private final float loadFactor;
// queue保存的是“已被GC清除”的“弱引用的键”。
// 弱引用和ReferenceQueue 是联合使用的:如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列queue中
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
//WeakHashMap被改变的次数
int modCount;
构造方法
//指定容量和加载因子的构造函数
public WeakHashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Initial Capacity: "+
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load factor: "+
loadFactor);
int capacity = 1;
while (capacity < initialCapacity)
//找出“大于initialCapacity”的最小的2的幂,因为初始容量必须为2的幂
capacity <<= 1;
table = newTable(capacity);
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
}
/**
* Constructs a new, empty <tt>WeakHashMap</tt> with the given initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity The initial capacity of the <tt>WeakHashMap</tt>
* @throws IllegalArgumentException if the initial capacity is negative
*/
//指定容量大小的构造函数
public WeakHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs a new, empty <tt>WeakHashMap</tt> with the default initial
* capacity (16) and load factor (0.75).
*/
public WeakHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//含有子map的构造方法
public WeakHashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY),
DEFAULT_LOAD_FACTOR);
putAll(m);
}
重要方法
// 因为WeakHashMap中允许key为null,因此当插入null的key时,将其当做弱引用,并删除
//因此,这里对于“key为null”的清空,都统一替换为“key为NULL_KEY”,“NULL_KEY”是“静态的final常量”
private static final Object NULL_KEY = new Object();
//对key为null的key进行特殊处理
private static Object maskNull(Object key) {
return (key == null ? NULL_KEY : key);
}
//还原上一个特殊处理
static Object unmaskNull(Object key) {
return (key == NULL_KEY) ? null : key;
}
//判断x与y是否相等
private static boolean eq(Object x, Object y) {
return x == y || x.equals(y);
}
//返回索引值
//保证索引值一定小于length
private static int indexFor(int h, int length) {
return h & (length-1);
}
//清空table中的无用键值对
//1当WeakHashMap的某个弱引用不再被使用而被GC回收时,被回收的key就会被添加到ReferenceQueue(queue)中
//当执行expungeStaleEntries时,首先会先遍历ReferenceQueue(queue),然后就在“WeakReference的table”中删除与“ReferenceQueue(queue)中key”对应的键值对
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;
int i = indexFor(e.hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
while (p != null) {
Entry<K,V> next = p.next;
if (p == e) {
if (prev == e)
table[i] = next;
else
prev.next = next;
// Must not null out e.next;
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
//WeakHashMap中获取table数组
private Entry<K,V>[] getTable() {
//删除table中键为null的键值对
expungeStaleEntries();
return table;
}
//获取WeakHashMap的实际大小
public int size() {
if (size == 0)
return 0;
expungeStaleEntries();
return size;
}
//获取key对应的value值
public V get(Object key) {
//对key为null的值进行特殊处理
Object k = maskNull(key);
//获取key的hash值
int h = hash(k);
Entry<K,V>[] tab = getTable();
int index = indexFor(h, tab.length);
Entry<K,V> e = tab[index];
在“该hash值对应的链表”上查找“键值等于key”的元素
while (e != null) {
if (e.hash == h && eq(k, e.get()))
return e.value;
e = e.next;
}
return null;
}
//返回指定key值的键值对
Entry<K,V> getEntry(Object key) {
Object k = maskNull(key);
//获取其哈希值
int h = hash(k);
Entry<K,V>[] tab = getTable();
//获取其索引值
int index = indexFor(h, tab.length);
//取得该索引值对应数组的单链表
Entry<K,V> e = tab[index];
//遍历该单链表,直到对应的哈希值和值都相等
while (e != null && !(e.hash == h && eq(k, e.get())))
e = e.next;
return e;
}
//将“key-value”添加到WeakHashMap中
public V put(K key, V value) {
Object k = maskNull(key);
int h = hash(k);
Entry<K,V>[] tab = getTable();
int i = indexFor(h, tab.length);
for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出
if (h == e.hash && eq(k, e.get())) {
V oldValue = e.value;
if (value != oldValue)
e.value = value;
return oldValue;
}
}
// 若“该key”对应的键值对不存在于WeakHashMap中,则将“key-value”添加到table中
modCount++;
Entry<K,V> e = tab[i];
tab[i] = new Entry<>(k, value, queue, h, e);
if (++size >= threshold)
resize(tab.length * 2);
return null;
}
//重新调整大小
void resize(int newCapacity) {
Entry<K,V>[] oldTable = getTable();
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry<K,V>[] newTable = newTable(newCapacity);
transfer(oldTable, newTable);
table = newTable;
/*
* If ignoring null elements and processing ref queue caused massive
* shrinkage, then restore old table. This should be rare, but avoids
* unbounded expansion of garbage-filled tables.
*/
if (size >= threshold / 2) {
threshold = (int)(newCapacity * loadFactor);
} else {
expungeStaleEntries();
transfer(newTable, oldTable);
table = oldTable;
}
}
总结:
WeakHashMap和HashMap都是通过"拉链法"实现的散列表。它们的源码绝大部分内容都一样,这里就只是对它们不同的部分就是说明。
WeakReference是“弱键”实现的哈希表。它这个“弱键”的目的就是:实现对“键值对”的动态回收。当“弱键”不再被使用到时,GC会回收它,WeakReference也会将“弱键”对应的键值对删除。
“弱键”是一个“弱引用(WeakReference)”,在Java中,WeakReference和ReferenceQueue 是联合使用的。在WeakHashMap中亦是如此:如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 接着,WeakHashMap会根据“引用队列”,来删除“WeakHashMap中已被GC回收的‘弱键’对应的键值对”。
WeakHashMap遍历
package test01;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
public class MapTest {
public static void main(String[] args) {
WeakHashMap<Integer, String> map = new WeakHashMap<>();
map.put(1, "Tiffany");
// 通过entrySet()获取键值对集合
// Iterator iter = map.entrySet().iterator();
// while(iter.hasNext()) {
// Map.Entry<Integer, String> entry = (Map.Entry<Integer, String>)iter.next();
// int i = entry.getKey();
// String s = entry.getValue();
// System.out.println(i+" "+s);
// }
// 通过keySet()获取WeakHashMap的key集合
// 通过Iterator迭代器来遍历key集合,get获取对应的值
// Iterator iter = map.keySet().iterator();
// while(iter.hasNext()) {
// int i = (int) iter.next();
// String s = map.get(i);
// System.out.println(i+" "+s);
// }
// 通过value建立Collection的值集合
// 通过迭代器遍历上一步得到的集合
Collection<String> c = map.values();
Iterator<String> iter = c.iterator();
while(iter.hasNext()) {
String s = iter.next();
System.out.println(s);
}
}
}
HashTable
HashTable简介
和HashMap一样,HashTable也是散列表,它存储的是键值对映射。与HashMap不同的是,HashTable中的实现方法都是线程同步的,所以HashTable是线程安全的。
HashTable中的key和value都不可以为null,且映射不是有序的。
Hashtable 的实例有两个参数影响其性能:初始容量 和 加载因子。容量 是哈希表中桶 的数量,初始容量 就是哈希表创建时的容量。注意,哈希表的状态为 open:在发生“哈希冲突”的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索。加载因子 是对哈希表在其容量自动增加之前可以达到多满的一个尺度。初始容量和加载因子这两个参数只是对该实现的提示。关于何时以及是否调用 rehash 方法的具体细节则依赖于该实现。
通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查找某个条目的时间(在大多数 Hashtable 操作中,包括 get 和 put 操作,都反映了这一点)。
HashTable的数据结构
HashTable的继承关系
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
HashTable的构造方法
// 默认构造函数。
public Hashtable()
// 指定“容量大小”的构造函数
public Hashtable(int initialCapacity)
// 指定“容量大小”和“加载因子”的构造函数
public Hashtable(int initialCapacity, float loadFactor)
// 包含“子Map”的构造函数
public Hashtable(Map<? extends K, ? extends V> t)
HashTable与HashMap采用Node存储键值对不同的是,HashTable采用的结构是Entry数组,组合成单向链表,实现了Map的内部接口Entry,HashTable是通过拉链法来解决hash冲突的
private static class Entry<K,V> implements Map.Entry<K,V>
private static class Entry<K,V> implements Map.Entry<K,V> {
//每一个HashTable的Entry结构都包含四部分,hash值,key值,value值,指向下一个Entry的next指针
final int hash;
final K key;
V value;
Entry<K,V> next;
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
HashTable主要源码解析
首先HashTable类有如下几个成员变量:table, count, threshold, loadFactor, modCount。
table是一个Entry[]数组类型,而Entry实际上就是一个单向链表。哈希表的"key-value键值对"都是存储在Entry数组中的。
count是Hashtable的大小,它是Hashtable保存的键值对的数量。
threshold是Hashtable的阈值,用于判断是否需要调整Hashtable的容量。threshold的值="容量*加载因子"。
loadFactor就是加载因子。
modCount是用来实现fail-fast机制的
private transient Entry<?,?>[] table;
/**
* The total number of entries in the hash table.
*/
private transient int count;
/**
* The table is rehashed when its size exceeds this threshold. (The
* value of this field is (int)(capacity * loadFactor).)
*
* @serial
*/
private int threshold;
/**
* The load factor for the hashtable.
*
* @serial
*/
private float loadFactor;
HashTable主要对外接口:
clear() 的作用是清空Hashtable。它是将Hashtable的table数组的值全部设为null
public synchronized void clear() {
Entry<?,?> tab[] = table;
modCount++;
for (int index = tab.length; --index >= 0; )
tab[index] = null;
count = 0;
}
contains() 和 containsValue() 的作用都是判断Hashtable是否包含“值(value)”
public synchronized boolean contains(Object value) {
if (value == null) {//如果值为空,那么就抛出空指针异常因为HashTable的键值对不能为空
throw new NullPointerException();
}
Entry<?,?> tab[] = table;//将table数组复制到tab中
for (int i = tab.length ; i-- > 0 ;) {//从后往前遍历数组,如果结点值等于value返回true
for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
if (e.value.equals(value)) {
return true;
}
}
}
return false;
}
public boolean containsValue(Object value) {
return contains(value);
}
containsKey() 的作用是判断Hashtable是否包含key
public synchronized boolean containsKey(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;//计算索引值,并防止下标越界
//找到与index对应的Entry(链表),接下来在链表中找出hash值和key值都相同的元素,返回true
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return true;
}
}
return false;
}
elements() 的作用是返回“所有value”的枚举对象
public synchronized Enumeration<V> elements() {
return this.<V>getEnumeration(VALUES);
}
// 获取Hashtable的枚举类对象
private <T> Enumeration<T> getEnumeration(int type) {
if (count == 0) {//如果HashTable的实际大小为0时,返回空枚举类对象
return (Enumeration<T>)emptyEnumerator;
} else {//否则返回正常的枚举类对象
return new Enumerator<T>(type, false);
}
}
接下来了解一下emptyEnumerator是如何实现的
// 空枚举类,是Collections的内部类,实现了Enumeration接口
// 当Hashtable的实际大小为0;此时,又要通过Enumeration遍历Hashtable时,返回的是“空枚举类”的对象。
private static class EmptyEnumerator implements Enumeration<Object> {
EmptyEnumerator() {
}
// 空枚举类的hasMoreElements() 始终返回false
public boolean hasMoreElements() {
return false;
}
// 空枚举类的nextElement() 抛出异常
public Object nextElement() {
throw new NoSuchElementException("Hashtable Enumerator");
}
}
get() 的作用就是获取key对应的value,没有的话返回null
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
put() 的作用是对外提供接口,让Hashtable对象可以通过put()将“key-value”添加到Hashtable中。
public synchronized V put(K key, V value) {
// Make sure the value is not null确保HashTable中的值不为null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.如果HashTable中存在键为key的键值对,则用新的value替换旧的value,并返回旧的value
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;
}
}
//如果HashTable中不存在键为key的键值对,那么先将修改统计数加1
addEntry(hash, key, value, index);
{private void addEntry(int hash, K key, V value, int index) {
modCount++;
//如果实际容量大于阈值,那么就重新调整HashTable的大小
Entry<?,?> tab[] = table;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
将“Hashtable中index”位置的Entry(链表)保存到e中
Entry<K,V> e = (Entry<K,V>) tab[index];
//创建“新的Entry节点”,并将“新的Entry”插入“Hashtable的index位置”,并设置e为“新的Entry”的下一个元素(即“新Entry”为链表表头)
tab[index] = new Entry<>(hash, key, value, e);
count++;//计数加1
}}
return null;
}
remove() 的作用就是删除Hashtable中键为key的元素,并返回被删除的值
public synchronized V remove(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
// 找到“key对应的Entry(链表)”
// 然后在链表中找出要删除的节点,并删除该节点。
Entry<K,V> e = (Entry<K,V>)tab[index];
//设一个空的Entry,将e赋值给他,遍历单向链表
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.value = null;
return oldValue;
}
}
return null;
}
Hashtable实现了Cloneable接口,即实现了clone()方法。
clone()方法的作用很简单,就是克隆一个Hashtable对象并返回。
public synchronized Object clone() {
try {
Hashtable<?,?> t = (Hashtable<?,?>)super.clone();
t.table = new Entry<?,?>[table.length];
for (int i = table.length ; i-- > 0 ; ) {
t.table[i] = (table[i] != null)
? (Entry<?,?>) table[i].clone() : null;
}
t.keySet = null;
t.entrySet = null;
t.values = null;
t.modCount = 0;
return t;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
Hashtable实现java.io.Serializable,分别实现了串行读取、写入功能。
串行写入函数就是将Hashtable的“总的容量,实际容量,所有的Entry”都写入到输出流中
串行读取函数:根据写入方式读出将Hashtable的“总的容量,实际容量,所有的Entry”依次读出