Java集合之Map接口

目录

框架注释:

Map接口

Map.Entry接口

AbstractMap抽象类

SortedMap接口

Dictionary抽象类

HashMap

HashMap简介

HashMap数据结构

HashMap源码解析

WeakHashMap

WeakHashMap简介

WeakHashMap的数据结构

WeakHashMap源码分析

WeakHashMap遍历

HashTable

HashTable简介

HashTable的数据结构

HashTable主要源码解析


 


框架注释:

  • 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 类型参数的构造方法,它创建一个新的有序映射,其键-值映射关系和排序方法与输入的有序映射相同。无法保证强制实施此建议,因为接口不能包含构造方法。

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”依次读出

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值