集合类---Map

Map常用的子类:

一、HashMap详解

 1.特点

1)线程不安全。如果想要得到线程安全的HashMap,可以使用Collections的静态方法:Map map = Collections.synchronizedMap(new HashMap());

2)允许null键和null值。只能有一个键为null,可以有多个值为null。如果get()返回null,可以表示没有该键,也可以表示该键所对应的值为null。所以应该用containsKey()或containsValue()来判断是否存在。

3)时间复杂度是o(1)。

4)初始容量和装填因子影响其性能。

5)无序。即不能按照输入的顺序输出,而LinkedHashMap可以(双向链表对其进行保证)。

6)没有contains()方法

2.数据结构

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

HashMap的底层是哈希数组,数组元素为Entry。HashMap通过key的hashCode来计算hash值,当hashCode相同时,通过“拉链法”解决冲突。当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。原本Map.Entry接口的实现类Entry改名为了Node。转化为红黑树时改用另一种实现TreeNode。 

Node类:

 1     static class Node<K,V> implements Map.Entry<K,V> {
 2         final int hash;//hash值
 3         final K key;
 4         V value;
 5         Node<K,V> next;//指向下一个节点
 6 
 7         Node(int hash, K key, V value, Node<K,V> next) {
 8             this.hash = hash;
 9             this.key = key;
10             this.value = value;
11             this.next = next;
12         }
13 
14         public final K getKey()        { return key; }
15         public final V getValue()      { return value; }
16         public final String toString() { return key + "=" + value; }
17         //重写hashCode
18         public final int hashCode() {
19             return Objects.hashCode(key) ^ Objects.hashCode(value);
20         }
21 
22         public final V setValue(V newValue) {
23             V oldValue = value;
24             value = newValue;
25             return oldValue;
26         }
27         //比较key和value是否equals相等
28         public final boolean equals(Object o) {
29             if (o == this)
30                 return true;
31             if (o instanceof Map.Entry) {
32                 Map.Entry<?,?> e = (Map.Entry<?,?>)o;
33                 if (Objects.equals(key, e.getKey()) &&
34                     Objects.equals(value, e.getValue()))
35                     return true;
36             }
37             return false;
38         }
39     }
View Code

3.源码解读

(1)主要属性

 1 //hash表的桶,用一个数组模拟,数组中每个元素都是一个指针,可以是单链表的头指针,也可以是红黑树的根节点的指针  
 2 transient Node<K,V>[] table;
 3 
 4 //用来实现遍历map的set,依次遍历所有的node或者treeNode 
 5 transient Set<Map.Entry<K,V>> entrySet;  
 6 
 7 //该map中所有key-value对的个数
 8 transient int size; 
 9 
10 //修改次数,用来判断该map是否同时被多个线程操作,多线程环境下会抛出异常ConcurrentModificationException
11 transient int modCount;  
12 
13 //= table.size()* loadFactor,当table中实际占用量(不是table中占用的bin的个数,而是所有bin中的Node的总数)超过threshold时,就会进行resize()操作  
14 //eg:table.size()=16,loadFactor=0.75,则当所有Bin中的node的个数超过12时会进行resize.  
15 //table的容量,如果没有设置,则默认等于DEFAULT_INITIAL_CAPACITY=16,且必须为2的整数次幂。  
16 int threshold;
17 
18 //加载因子<=1,当table中实际占用的容量超过table.size()* loadFactor时,会进行table的扩容。默认加载因子为DEFAULT_LOAD_FACTOR=0.75  
19 final float loadFactor;
20   
21 //table默认初始容量 16,必须是2的整数次幂  
22 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 
23 
24 //table的最大容量  
25 static final int MAXIMUM_CAPACITY = 1 << 30;
26 
27 //默认加载因子,当table数组的容量超过table.length* loadFactor时,会调用resize()进行扩容。  
28 static final float DEFAULT_LOAD_FACTOR = 0.75f;
29   
30 //当table[i]中的node个数超过8个,会将单链表table[i]转化成红黑树  
31 static final int TREEIFY_THRESHOLD = 8;
32 
33 //当table[i]中红黑树的节点数少于6时,会退化成单链表  
34 static final int UNTREEIFY_THRESHOLD = 6;
35 
36 //当table的length大于64时,才会进行将某条node个数超过8的单链表转化成红黑树操作  
37 static final int MIN_TREEIFY_CAPACITY = 64;
View Code

(2)构造方法

 1     /**
 2      * 根据初始化容量和加载因子构建一个空的HashMap.
 3      */
 4     public HashMap(int initialCapacity, float loadFactor) {
 5         if (initialCapacity < 0)
 6             throw new IllegalArgumentException("Illegal initial capacity: " +
 7                                                initialCapacity);
 8 //设置的初始容量大于最大允许容量,则强制将initialCapacity = MAXIMUM_CAPACITY  
 9         if (initialCapacity > MAXIMUM_CAPACITY)
10             initialCapacity = MAXIMUM_CAPACITY;
11         if (loadFactor <= 0 || Float.isNaN(loadFactor))
12             throw new IllegalArgumentException("Illegal load factor: " +
13                                                loadFactor);
14         this.loadFactor = loadFactor;
15 //调用tableSizeFor将threshold设置成大于initialCapacity的最小的2的整数次幂  
16         this.threshold = tableSizeFor(initialCapacity);
17     }
18 
19     /**
20      * 使用初始化容量和默认加载因子(0.75).
21      */
22     public HashMap(int initialCapacity) {
23         this(initialCapacity, DEFAULT_LOAD_FACTOR);
24     }
25 
26     /**
27      * 使用默认初始化大小(16)和默认加载因子(0.75).
28      */
29     public HashMap() {
30         this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
31     }
32 
33     /**
34      * 用已有的Map构造一个新的HashMap.
35      */
36     public HashMap(Map<? extends K, ? extends V> m) {
37         this.loadFactor = DEFAULT_LOAD_FACTOR;
38         putMapEntries(m, false);
39     }
View Code

(3)重要方法

1)put方法

  • 如果是首个插入map的节点,map进行初始化,在resize中进行
  • 如果是put一个新节点,则插入结束后检查对应的bin是否需要转化成红黑树
  • 插入或更新结束后,检查该table是否需要resize扩容
 1 public V put(K key, V value) {  
 2       return putVal(hash(key), key, value, false, true);  
 3   }  
 4 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,  
 5                  boolean evict) {  
 6       Node<K,V>[] tab; Node<K,V> p; int n, i;  
 7 //如果table为null或者table的长度为0,则调用resize()对table进行初始化  
 8       if ((tab = table) == null || (n = tab.length) == 0)  
 9           n = (tab = resize()).length;  
10 //如果hash号桶中没有node,则将该节点作为首节点放入该桶中  
11       if ((p = tab[i = (n - 1) & hash]) == null)  
12           tab[i] = newNode(hash, key, value, null);  
13 //如果hash号桶中有node,则遍历后添加
14       else {  
15           Node<K,V> e; K k;  
16 //如果key和value都相等,则直接覆盖
17           if (p.hash == hash &&  
18               ((k = p.key) == key || (key != null && key.equals(k))))  
19               e = p;  
20 //在红黑树中查找,e指向查找到的treeNode,向红黑树中添加  
21           else if (p instanceof TreeNode)  
22               e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);  
23 //在单链表中查找
24           else {  
25               for (int binCount = 0; ; ++binCount) {  
26 //当遍历该桶没有找到相同的key时,就新new一个Node,加入到链表末尾
27                   if ((e = p.next) == null) {  
28                       p.next = newNode(hash, key, value, null);  
29 //检查该链表中Node的个数是否>=8,是则要将单链表转化成红黑树  
30                       if (binCount >= TREEIFY_THRESHOLD - 1) 
31                           treeifyBin(tab, hash);  
32                       break;  
33                   }  
34 //在单链表如果key和value都相等,则直接覆盖
35                   if (e.hash == hash &&  
36                       ((k = e.key) == key || (key != null && key.equals(k))))  
37                       break;  
38                   p = e;  
39               }  
40           }  
41           if (e != null) { // existing mapping for key  
42               V oldValue = e.value;  
43         //更新Node的value  
44               if (!onlyIfAbsent || oldValue == null)  
45                   e.value = value;  
46         //回调接口,让LinkedHashMap执行对应的动作。在hashMap中没有动作:void  
47               afterNodeAccess(e);  
48               return oldValue;  
49           }  
50       }  
51       ++modCount;  
52 //检查table的size有没有超过临界值,超过要进行resize扩容  
53       if (++size > threshold)  
54           resize();  
55 //回调接口,让LinkedHashMap执行对应的动作。在hashMap中没有动作:void  
56       afterNodeInsertion(evict);  
57       return null;  
58   }
View Code

2)get方法

 1     public V get(Object key) {
 2         Node<K,V> e;
 3         return (e = getNode(hash(key), key)) == null ? null : e.value;
 4     }
 5 
 6     /**
 7      * Implements Map.get and related methods
 8      *
 9      * @param hash hash for key
10      * @param key the key
11      * @return the node, or null if none
12      */
13     final Node<K,V> getNode(int hash, Object key) {
14         Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
15 //如果table不为空,table.length>0,且hash对应的桶中有值  
16         if ((tab = table) != null && (n = tab.length) > 0 &&
17             (first = tab[(n - 1) & hash]) != null) {
18 //如果第一个节点就是要查找的节点,则返回第一个节点
19             if (first.hash == hash && // always check first node
20                 ((k = first.key) == key || (key != null && key.equals(k))))
21                 return first;
22 //否则依次遍历后来的节点
23             if ((e = first.next) != null) {
24 //查找红黑树
25                 if (first instanceof TreeNode) 
26                     return ((TreeNode<K,V>)first).getTreeNode(hash, key);
27 //查找单链表
28                 do {
29                     if (e.hash == hash &&
30                         ((k = e.key) == key || (key != null && key.equals(k))))
31                         return e;
32                 } while ((e = e.next) != null);
33             }
34         }
35         return null;
36     }
37 
38     // 遍历红黑树搜索节点
39     /**
40      * Calls find for root node.
41      */
42     final TreeNode<K,V> getTreeNode(int h, Object k) {
43         return ((parent != null) ? root() : this).find(h, k, null);
44     }
45 
46     /**
47      * Returns root of tree containing this node.
48      */
49     final TreeNode<K,V> root() {
50         for (TreeNode<K,V> r = this, p;;) {
51             if ((p = r.parent) == null)
52                 return r;
53             r = p;
54         }
55     }
56 
57     /**
58      * Finds the node starting at root p with the given hash and key.
59      * The kc argument caches comparableClassFor(key) upon first use
60      * comparing keys.
61      */
62     final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
63         TreeNode<K,V> p = this;
64         do {
65             int ph, dir; K pk;
66             TreeNode<K,V> pl = p.left, pr = p.right, q;
67             if ((ph = p.hash) > h) // 当前节点hash大
68                 p = pl; // 查左子树
69             else if (ph < h) // 当前节点hash小
70                 p = pr; // 查右子树
71             else if ((pk = p.key) == k || (k != null && k.equals(pk)))
72                 return p; // hash、key都相等,即找到,返回当前节点
73             else if (pl == null) // hash相等,key不等,左子树为null,查右子树
74                 p = pr;
75             else if (pr == null)
76                 p = pl;
77             else if ((kc != null ||
78                       (kc = comparableClassFor(k)) != null) &&
79                      (dir = compareComparables(kc, k, pk)) != 0)
80                 p = (dir < 0) ? pl : pr;
81             else if ((q = pr.find(h, k, kc)) != null)
82                 return q;
83             else
84                 p = pl;
85         } while (p != null);
86         return null;
87     }
View Code

3)remove方法

 1 public V remove(Object key) {  
 2         Node<K,V> e;  
 3         return (e = removeNode(hash(key), key, null, false, true)) == null ?  
 4             null : e.value;  
 5     }  
 6 //removeNode:删除指定的key所对应的Node,matchValue为true表示只有key和value都对应才删除;movable表示在删除时不移动其他node  
 7   
 8 final Node<K,V> removeNode(int hash, Object key, Object value,  
 9                                boolean matchValue, boolean movable) {  
10         Node<K,V>[] tab; Node<K,V> p; int n, index;  
11         //只有在table不为空,table的length大于0,hash&(n-1)号桶中有值时才进行查找删除  
12         if ((tab = table) != null && (n = tab.length) > 0 &&  
13             (p = tab[index = (n - 1) & hash]) != null) {  
14             Node<K,V> node = null, e; K k; V v;  
15             //如果头结点是查找的节点则令node指向它  
16             if (p.hash == hash &&  
17                 ((k = p.key) == key || (key != null && key.equals(k))))  
18                 node = p;  
19             else if ((e = p.next) != null) {  
20                 //如果这个桶中存储的是红黑树,则通过getTreeNode找到对应的node  
21                 if (p instanceof TreeNode)  
22                     node = ((TreeNode<K,V>)p).getTreeNode(hash, key);  
23                 else {  
24                     //如果是单链表,则依次查找node,知道找到,令node指向它  
25                     do {  
26                         if (e.hash == hash &&  
27                             ((k = e.key) == key ||  
28                              (key != null && key.equals(k)))) {  
29                             node = e;  
30                             break;  
31                         }  
32                         p = e;  
33                     } while ((e = e.next) != null);  
34                 }  
35             }  
36             //当Node==null表示没有找到该节点  
37             //只有当不要求匹配value或者要求匹配并且node的value也相等时,才进行删除  
38             if (node != null && (!matchValue || (v = node.value) == value ||  
39                                  (value != null && value.equals(v)))) {  
40                 //在红黑树中删除节点,把movable传递进去  
41                 if (node instanceof TreeNode)  
42                     ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);  
43                 //头结点删除  
44                 else if (node == p)  
45                     tab[index] = node.next;  
46                 //p指向node的前一个节点  
47                 else  
48                     p.next = node.next;  
49                 ++modCount;  
50                 --size;  
51                 //回调接口,让LinkedHashMap执行对应的动作。在hashMap中没有动作:void afterNodeRemoval(Node<K,V> p) { }  
52                 afterNodeRemoval(node);  
53                 return node;  
54             }  
55         }  
56         return null;  
57     }  
View Code

 4)resize方法

  • 若table没有初始化,则采用默认的cap和加载因子(或者使用new map对象时传递进来的thr和f)进行初始化,为table申请空间
  • 若table已经初始化,则将cap*2,thr*2。同时若cap已经超过了MAX_CAP,则直接将thr设置与cap相等。
  • 将oldTable中的node都映射到newTable中。OldTable[i]中的node要么映射到newTable的i中,要么是i+oldcap中,由node.hash的第oldCap二进制中1所在位置j的那个位置bit值决定。
 1 final Node<K,V>[] resize() {  
 2         Node<K,V>[] oldTab = table;  
 3         //cap是数组的容量,thr是数组进行扩容的临界值  
 4         int oldCap = (oldTab == null) ? 0 : oldTab.length;  
 5         int oldThr = threshold;  
 6         int newCap, newThr = 0;  
 7         //不是数组初始化  
 8         if (oldCap > 0) {  
 9             //当原table的size已经达到MAXIMUM_CAPACITY时,不对cap进行调整,只是将扩容临界值thr调整为与cap相同,让table空间得到100%的利用  
10             if (oldCap >= MAXIMUM_CAPACITY) {  
11                 threshold = Integer.MAX_VALUE;  
12                 return oldTab;  
13             }  
14             //否则就将cap和thr进行*2扩容  
15             //即使newCap<<1,它的值也始终小于Integer.MAX_VALUE,因为newCap是一个int型的整数  
16             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&  
17                      oldCap >= DEFAULT_INITIAL_CAPACITY)  
18                 newThr = oldThr << 1; // double threshold  
19         }  
20         //通过初始化传递了thr但是还没有进行table的初始化操作,这时将cap设置为oldCap的值  
21         else if (oldThr > 0) // initial capacity was placed in threshold  
22             newCap = oldThr;  
23 //初始化没有传递任何参数,cap和thr都采用默认值  
24         else {               // zero initial threshold signifies using defaults  
25             newCap = DEFAULT_INITIAL_CAPACITY;  
26             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);  
27         }  
28         //如果上面没有设置newThr的值,这里统一进行处理.  
29         //主要有两个地方,第一个else if ((newCap = oldCap << 1),第二个else if (oldThr > 0);    
30       if (newThr == 0) {  
31             float ft = (float)newCap * loadFactor;  
32             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?  
33                       (int)ft : Integer.MAX_VALUE);  
34         }  
35         //重新设置扩容后的临界值  
36         threshold = newThr;  
37         //利用上面计算的newCap重新为数组申请扩容后的空间  
38         @SuppressWarnings({"rawtypes","unchecked"})  
39             Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];  
40         table = newTab;  
41         //将原数组中内容转移到新数组中  
42         if (oldTab != null) {  
43             //原table中每个桶中的node逐个进行重新hash处理  
44             for (int j = 0; j < oldCap; ++j) {  
45                 Node<K,V> e;  
46                 if ((e = oldTab[j]) != null) {  
47                     //将原table中每个链表的头指针置null  
48                     oldTab[j] = null;  
49                     //如果oldTable[i]这条链表只有头指针,则将该node重新hash映射到新链表的某个位置上  
50                     if (e.next == null)  
51                         newTab[e.hash & (newCap - 1)] = e;  
52                     //如果该条链表是以红黑树的形式存储的,则调用红黑树的相关操作  
53                     else if (e instanceof TreeNode)  
54                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);  
55                     //对oldTable[i]链表中每个元素逐一处理  
56                     else { // preserve order  
57                         //lo表示原链表中的node在新链表中的映射仍在同一位置table[i]  
58                         Node<K,V> loHead = null, loTail = null;  
59                         //hi 表示原链表中的node在新链表中的映射在table[i+oldCap]。只有这两种位置  
60                         Node<K,V> hiHead = null, hiTail = null;  
61                         Node<K,V> next;  
62                         do {  
63                             next = e.next;  
64                             //i=e.hash&(oldCap-1),那么e.hash&oldCap的值为e.hash在oldCap二进制形式中那个唯一(cap的值都为2的整数次幂,所以二进制形式中只有一个1)的1所在位置的值,要么为0,要么为1.。  
65                             //因此,oldTable中的table[i]中的node,在新链表中的位置要么为i,要么为i+oldCap  
66                             //之所以直接采用(e.hash & oldCap)是为了计算简便,避免每次都用e.hash&(newCap-1)  
67 if ((e.hash & oldCap) == 0) {  
68                                 if (loTail == null)  
69                                     loHead = e;  
70                                 else  
71                                     loTail.next = e;  
72                                 loTail = e;  
73                             }  
74                             else {  
75                                 if (hiTail == null)  
76                                     hiHead = e;  
77                                 else  
78                                     hiTail.next = e;  
79                                 hiTail = e;  
80                             }  
81                         } while ((e = next) != null);  
82                         //在新table中映射位置为j的,相对顺序与oldTable中的相同  
83                         if (loTail != null) {  
84                             loTail.next = null;  
85                             newTab[j] = loHead;  
86                         }  
87                         //在新链表中映射位置为j+oldCap的  
88                         if (hiTail != null) {  
89                             hiTail.next = null;  
90                             newTab[j + oldCap] = hiHead;  
91                         }  
92                     }  
93                 }  
94             }  
95         }  
96         return newTab;  
97     }  
View Code

5)treeifyBin和untreeify方法

treeifyBin将某个hash桶的单链表转化成红黑树;untreeify将红黑树转化成单链表,在删除红黑树节点时会用到
基本步骤:
a、检查table.length是否>=64,如果不成立,则进行resize扩容,结束。
b、通过hash&(n-1)定位到table相应的bin中,检查bin中是否有Node,将单链表中的Node类型依次转化成treenode类型,并链接在一个双链表中
c、调用treeNode.treeify方法将该桶中的treeNode双链表转化成红黑树。

 1    final void treeifyBin(Node<K,V>[] tab, int hash) {  
 2        int n, index; Node<K,V> e;  
 3     //若table的len没有达到最小树化值,则进行扩容处理  
 4        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)  
 5            resize();  
 6     //若该桶中有node,则将该桶中的单链表转化成红黑树  
 7        else if ((e = tab[index = (n - 1) & hash]) != null) {  
 8            TreeNode<K,V> hd = null, tl = null;  
 9            do {  
10             //将每个node节点的值重新生成一个TreeNode节点  
11                TreeNode<K,V> p = replacementTreeNode(e, null);  
12             //hd指向红黑树的根节点  
13                if (tl == null)  
14                    hd = p;  
15                else {  
16                 //prev指向前一个节点,next指向后一个节点  
17                    p.prev = tl;  
18                    tl.next = p;  
19                }  
20                tl = p;  
21            } while ((e = e.next) != null);  
22         //上面的while循环将单链表转化成了双链表,节点类型由node编程了treeNode,hd指向头结点  
23            if ((tab[index] = hd) != null)  
24             //将该双链表构建成红黑树  
25                hd.treeify(tab);  
26        }  
27    }  
28 //红黑树退化成单链表  
29 //a、通过treeNode.next遍历红黑树,并将节点依次replacementNode成Node类型。  
30 //b、将转化后的节点依次链接成一条单链表,返回头结点的指针  
31  final Node<K,V> untreeify(HashMap<K,V> map) {  
32            Node<K,V> hd = null, tl = null;  
33            for (Node<K,V> q = this; q != null; q = q.next) {  
34             //将红黑树中的节点treeNode依次转化成node  
35                Node<K,V> p = map.replacementNode(q, null);  
36                if (tl == null)  
37                    hd = p;  
38                else  
39                    tl.next = p;  
40                tl = p;  
41            }  
42         //返回单链表的头结点  
43            return hd;  
44        }  
View Code

常用遍历方法:

https://blog.csdn.net/shenshen123jun/article/details/9074523

Hashmap 不是线程安全的,所以如果在使用迭代器的过程中有其他线程修改了 map, 则会抛出 ConcurrentModificationException,这就是所谓的 fast-fail 策略,是通过 modcount 实现,对 Hashmap 内容的修改都将增加这个值,那么在迭代器初始化时会将这个值赋给迭代器的 exceptedmodcount。在迭代过程中,判断 modcount 和 exceptedmodcount 是否相等, 若不相等,则说明其他线程修改了 map。Modcount 是 volatile 的。

二、HashTable详解

1.特点

1)线程安全。put,get,remove等方法上都有synchronized关键字保持同步。

2)不允许null键和null值,如果存入会报空指针异常。

2.数据结构

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable

3.源码解读

(1)主要属性

 1 private transient Entry<?,?>[] table  
 2 private transient int count;//table中所有entry的个数  
 3 private int threshold;//扩容的临界值count >= threshold,threshold=loadFactor*capacity,  
 4 private float loadFactor;//加载因子threshold=loadFactor*capacity,加载因子越大,table的利用率越高  
 5 private transient int modCount = 0;//单线程,多线程操作可能会引起Hashtable fail-fast,抛出ConcurrentModificationException异常  
 6   
 7 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//table的最大size  
 8   
 9 //三种遍历方式  
10 private transient volatile Set<K> keySet;  
11 private transient volatile Set<Map.Entry<K,V>> entrySet;  
12 private transient volatile Collection<V> values;  
View Code

(2)构造方法

 1 //传递一个table的初始容量和加载因子,table的初始化在构造函数中进行(与HashMap不同,hashmap在put第一个node中进行)。  
 2  public Hashtable(int initialCapacity, float loadFactor) {  
 3         if (initialCapacity < 0)  
 4             throw new IllegalArgumentException("Illegal Capacity: "+  
 5                                                initialCapacity);  
 6         if (loadFactor <= 0 || Float.isNaN(loadFactor))  
 7             throw new IllegalArgumentException("Illegal Load: "+loadFactor);  
 8   
 9         if (initialCapacity==0)  
10             initialCapacity = 1;  
11         //设置loadFactor,threshold的值,为table申请空间  
12         this.loadFactor = loadFactor;  
13         //与HashMap不同,table的size可以随意指定,不用为2的整数次幂,甚至都不用超过11(默认的初始容量)  
14         table = new Entry<?,?>[initialCapacity];  
15         threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);  
16     }  
17 //默认的loadFactor=0.75  
18 public Hashtable(int initialCapacity) {  
19         this(initialCapacity, 0.75f);  
20     }  
21 //默认的capacity=11  
22 public Hashtable() {  
23         this(11, 0.75f);  
24     }  
25 //2*t.size()保证不用插进去就需要扩容  
26 public Hashtable(Map<? extends K, ? extends V> t) {  
27         this(Math.max(2*t.size(), 11), 0.75f);  
28         putAll(t);  
29     }  
View Code

(3)不同方法

1)rehash:扩容,不是同步的
思路:
将capatity*2,根据loadfactor重新设置threshold
依次逆序遍历oldMap中所有Bin中的节点,重新映射到newMap的bin中。这里是从链表头插入,所以顺序与OldMap中的相反。hashMap是插入链表尾部,顺序相同。

与hashMap的resize不同点:
(1)hashMap的resize中可能对table进行初始化,这里初始化在构造函数中进行
(2)扩容方式:hashmap中 newCap=oldCap*2,这样保证新容量依然是2的整数次幂;hashTable中,newCap=oldCap*2+1
(3)映射方式:hashMap中oldMap中同一个bin中node只可能映射到i和i+oldCap两个桶中;hashTable中由于不要求cap是2的整数次幂,所以oldCap中同一个bin中可能映射到newMap的各个Bin中
(4)转移数据方式:hashMap中oldMap和newMap中node在链表中相对顺序不变,是在链表尾部插入的;hashTable中顺序逆向了,在链表首部插入的。
(5)效率:hashMap效率更高,采用&操作映射;HashTable效率低,采用%
(6)桶的结构:hashMap中当节点数大于8时,单链表会转化成红黑树;HashTable始终以单链表存储

 1 protected void rehash() {  
 2         int oldCapacity = table.length;  
 3         Entry<?,?>[] oldMap = table;  
 4   
 5         // 扩容方法:新容量=就容量*2+1  
 6         int newCapacity = (oldCapacity << 1) + 1;  
 7         if (newCapacity - MAX_ARRAY_SIZE > 0) {  
 8             if (oldCapacity == MAX_ARRAY_SIZE)  
 9                 // Keep running with MAX_ARRAY_SIZE buckets  
10                 return;  
11             newCapacity = MAX_ARRAY_SIZE;  
12         }  
13         Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];  
14   
15         modCount++;  
16         //确定新的临界值threshold  
17         threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);  
18         table = newMap;  
19         //逆序遍历oldMap的桶中的所有节点,并依次映射到newMap中的桶  
20         for (int i = oldCapacity ; i-- > 0 ;) {  
21             for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {  
22                 Entry<K,V> e = old;  
23                 old = old.next;  
24                 //oldMap映射到newMap的bin的方法:直接取余  
25                 int index = (e.hash & 0x7FFFFFFF) % newCapacity;  
26                 //每次都从newMap的链表的头部插入  
27                 e.next = (Entry<K,V>)newMap[index];  
28                 newMap[index] = e;  
29             }  
30         }  
31     }  
View Code

三、TreeMap详解

1.特点

1)有序

2)非线程安全

3)时间复杂度是o(lgn)

2.数据结构

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable

四、LinkedHashMap详解

1.特点

1)保证插入顺序或访问顺序有序。

2)key和value都允许为空。

3)非线程安全。

4)LinkedHashMap=LinkedList+HashMap,多态实现。

https://blog.csdn.net/justloveyou_/article/details/71713781

 

注:  HashSet子类依靠hashCode()和equal()方法来区分重复元素。

     HashSet内部使用Map保存数据,即将HashSet的数据作为Map的key值保存,这也是HashSet中元素不能重复的原因。而Map中保存key值的,会去判断当前Map中是否含有该Key对象,内部是先通过key的hashCode,确定有相同的hashCode之后,再通过equals方法判断是否相同。

原文地址:https://www.cnblogs.com/cing/p/9179872.html