HashTable,HashMap,ConcurrentHashMap三个类的比较

1.HashTable

1.来源

自JDK1.0开始就有的类,继承自Direction类,并实现了Map接口,这一块可自行看源码

2.字段说明

private transient Entry<?,?>[] table;    一个存放数据的hashtable
private transient int count;    放在hashTable中的节点的总数
private int threshold;    需要rehash时的临界值(capacity * loadFactor)
private float loadFactor;   加载因子(默认0.75,在调用无加载因子构造方法时默认给定)
private transient int modCount = 0;  hashTable结构被修改的次数统计

3.主要方法

1)核心构造方法

    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);
    }

根据给定容量capacity和加载因子loadFactor,创建散列表,计算扩容临界值

2)另外两种形式的构造方法

   
    public Hashtable() {
        this(11, 0.75f);
    }

    
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

第一个无参构造方法,设定默认容量11,加载因子0.75,调用了核心的构造方法来创建散列表

第二个构造方法,根据给定容量capacity,和默认给定的加载因子0.75,调用了核心的构造方法来创建散列表

3)添加元素的put方法

    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        //判断该key已经存在,直接修改value的值,并结束方法返回
        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;
    }

    //插入元素
    private void addEntry(int hash, K key, V value, int index) {
        modCount++;

        Entry<?,?> tab[] = table;
        //判断count >= 临界值,进行rehash重新散列,并重新计算key的hash值和散列表中index位置
        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")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }

我们可以看到,1.这个方法是用synchronized关键字来修饰的,说明这个方法是对这个对象进行了加锁访问,也就是说是线程安全的。2.这个方法可以看到,如果value是null,是会抛出空指针异常的。3.根据计算出的hash值进行散列来插入元素,for循环用来判断若该key已经存在。4.若key不存在则调用addEntry方法进行元素的插入(插入元素如果有冲突,hashTable采用的是数组加链表的头插法),下面我们看下Entry的构造方法

4)Entry构造方法

    protected Entry(int hash, K key, V value, Entry<K,V> next) {
        this.hash = hash;
        this.key =  key;
        this.value = value;
        this.next = next;
    }

结合3中的方法,插入时取出了index位置的元素,并将他作为新插入节点的next节点,这样做的好处是省去了插入时查找末端元素的时间消耗

5)rehash

    protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // overflow-conscious code
        //扩容后,新capacity为原来的2倍+1
        int newCapacity = (oldCapacity << 1) + 1;
        //判断capacity是否达到边界
        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];

        modCount++;
        //重新计算扩容临界值
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        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.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }

 重新散列,扩容后为什么要2倍再+1呢,这是为了最大可能的使被散列的元素分布均匀

6)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.value = null;
                return oldValue;
            }
        }
        return null;
    }

这个方法也是用了synchronized来修饰,删除具体细节就是链表的元素删除

7)get方法

    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;
    }

这个方法也用了synchronized来修饰,这说明这个类足够安全。至于元素的查找主要就是数组下标为index的链表查找

8)contains方法

    public synchronized boolean contains(Object value) {
        if (value == null) {
            throw new NullPointerException();
        }

        Entry<?,?> tab[] = table;
        for (int i = tab.length ; i-- > 0 ;) {
            for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
                if (e.value.equals(value)) {
                    return true;
                }
            }
        }
        return false;
    }

这个方法,用了两个for循环来查找,第一个for循环用来遍历数组的下标,第二for循环用来查找数组下标下的链表中的元素

2.HashMap

1.来源

jdk1.2 继承自AbstractMap类,并实现了Map接口,AbstractMap类也实现了Map接口

2.字段说明

static final int DEFAULT_INITIAL_CAPACITY =1 <<4;  HashMap的默认容量,16

static final int MAXIMUM_CAPACITY =1 <<30;  HashMap的最大支持容量,2^30

static final float DEFAULT_LOAD_FACTOR =0.75f;  HashMap的默认加载因子

static final int TREEIFY_THRESHOLD =8;  Bucket中链表长度大于该默认值,转化为红黑树

static final int UNTREEIFY_THRESHOLD =6;  Bucket中红黑树存储的Node小于该默认值,转化为链表

static final int MIN_TREEIFY_CAPACITY =64; 桶中的Node被树化时最小的hash表容量。(当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。)

transient Node[] table;  存储元素的数组,总是2的n次幂

transient Set>entrySet;  存储具体元素的集

transient int size; HashMap中存储的键值对的数量

transient int modCount; HashMap扩容和结构改变的次数。

int threshold; 扩容的临界值,=容量*填充因子

final float loadFactor;  填充因子

3.主要方法

1)指定容量和负载因子的构造方法

public HashMap(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);

    this.loadFactor = loadFactor;

    this.threshold =tableSizeFor(initialCapacity);

}

这个构造方法来确定负载因子扩容临界值,最后调用tableSizeFor方法来计算扩容临界值

2)其他两种构造方法

//无参构造方法
public HashMap() {

    this.loadFactor =DEFAULT_LOAD_FACTOR;// all other fields defaulted

}

//指定容量的构造方法

public HashMap(int initialCapacity) {

    this(initialCapacity,DEFAULT_LOAD_FACTOR);

}

无参构造方法,设定了负载因子为0.75的默认值。指定容量的构造方法设定容量,将负载因子设置为默认值

这两种构造方法,最后会调用3中的指定容量和负载因子的构造方法

3)根据键值对数量获取HashMap容量方法   tableSizeFor

static final int tableSizeFor(int cap) {

    int n = cap -1;

    n |= n >>>1;

    n |= n >>>2;

    n |= n >>>4;

    n |= n >>>8;

    n |= n >>>16;

    return (n <0) ?1 : (n >=MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY : n +1;

}

tabSizeFor方法,主要根据传入的键值对容量,来返回大于容量的最小的二次幂数值。

算法如下:

将传入的容量-1:至于这里为什么需要减1,是为了防止cap已经是2的幂。如果cap已经是2的幂, 又没有执行这个减1操作,则执行完后面的几条无符号右移操作之后,返回的capacity将是这个cap的2倍。

假设原始n:    0001  xxxx xxxx xxxx

第一次右移1位+或运算:二进制序列出现至少两个连续的1,如 0001 1xxx xxxx xxxx;

第二次右移2位+或运算:二进制序列出现至少四个连续的1,如 0001 111x xxxx xxxx;

第三次右移4位+或运算:二进制序列出现至少八个连续的1, 如 0001 1111 1111 xxxx;

第四次右移8位+或运算:二进制序列至少出现16个连续的1,如 0001 1111 1111 1111;

第五次右移16位+或运算:二进制序列至少出现32个连续的1,如 0001 1111 1111 1111;

上述运算中,若出现右移后为0,则或运算得到的结果和原始值一致,则后续推导过程可以忽略。

此时可以保证,原始序列从包含1的最高位,到最低位,全部都变成了1.

最后+1,返回的结果就是大于原值的最小二次幂数。

4)hash方法

static final int hash(Object key) {

     int h;

     return (key ==null) ?0 : (h = key.hashCode()) ^ (h >>>16);

}

hash方法用传入的key的hashCode和hashCode无符号右移16位的结果,做异或运算后作为hash值返回。

注:之所以获取hashCode后,还需要和右移16位的hashCode做异或运算,原因是:在根据hash值获取键值对在bucket数组中的下标时,采用的算法是:index=h & (length-1),当数组的length较小时,只有低位能够参与到“与”运算中,但是将hashCode右移16位再与本身做异或获取到的hash,可以使高低位均能够参与到后面的与运算中。

下面图说明:

5)插入方法   putVal

final V putVal(int hash,K key,V value,boolean onlyIfAbsent,boolean evict) {

    //存储Node节点的数组tab,单个Node节点p,HashMap的容量n
    Node<K,V>[] tab; Node<K,V> p;int n, i;  

    //初始化数组桶table (首次进入初始化)
    if ((tab =table) ==null || (n = tab.length) ==0)                                             
        n = (tab = resize()).length;
    //如果数组桶中不包含要插入的元素,将新键值对作为新Node存入数组(当前tab中无冲突元素)
    if ((p = tab[i = (n -1) & hash]) ==null)       
        tab[i] = newNode(hash, key, value,null);
    else {//桶中包含要插入的元素(tab中已有元素,发生冲突)                                                                                                       
        //e用来保存当前key已存在时的节点
        Node<K,V> e;K k; 
        //如果key和链表第一个元素p的key相等
        if (p.hash == hash &&((k = p.key) == key || (key !=null && key.equals(k))))    
            e = p;
        //若p是TreeNode类型,则使用红黑树的方法插入到树中
        else if (pinstanceof TreeNode)                                               
            e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
        else {//键值对的引用不在链表的第一个节点,此时需要遍历链表                                                                                     
            for (int binCount =0; ; ++binCount) {
                //将p.next指向e,并判断p是否为最后一个节点,若是插入新节点,此时e==null
                if ((e = p.next) ==null) {              
                    p.next = newNode(hash, key, value,null);
                    // -1 for 1st   ,若链表长度大于等于8,变红黑树,退出遍历
                    if (binCount >=TREEIFY_THRESHOLD -1)
                        treeifyBin(tab, hash);
                    break;
                }
                //找到与当前key值相同的节点
                if (e.hash == hash &&((k = e.key) == key 
                    || (key !=null &&key.equals(k))))   
                    break;                                                                                                            
                    //退出遍历(此时,e指向与当前key值相同的旧节点)
                //将e指向p,便于下次遍历e = p.next
                p = e;                       

            }

        }
        //当e非空时,说明e是原来HashMap中的元素,具有和新节点一样的key值
        if (e !=null) {
            V oldValue = e.value;
            //onlyIfAbsent 表示是否仅在 oldValue 为 null 的情况下更新键值对的值
            if (!onlyIfAbsent || oldValue ==null)         
                e.value = value;
            //空实现,LinkedHashMap用
            afterNodeAccess(e); 

            return oldValue;

        }

    }

    ++modCount;        //HashMap结构更改,modCount+1

    if (++size >threshold)  //判断是否需要扩容

        resize();

    afterNodeInsertion(evict);   //空实现,LinkedHashMap用

    return null;

}

HashMap中进行存储的入口方法是:put(K,V),但是核心方法是putVal方法,该方法一共有以下步骤:

1.初始化数组桶

2.判断数组桶中对应下标是否无元素存在,是,就直接存入

3.若数组桶中对应下标有元素存在,则开始遍历,根据长度将元素存入链表尾部或树中。

4.判断是否需要扩容

6)扩容方法   resize

final Node[] resize() {

    Node[] oldTab =table;

    int oldCap = (oldTab ==null) ?0 : oldTab.length;             //原HashMap的容量

    int oldThr =threshold;                                                     //原HashMap的扩容临界值

    int newCap, newThr =0;

    if (oldCap >0) {                                                               //case1 : odlCap>0,说明桶数组已经初始化过

        if (oldCap >=MAXIMUM_CAPACITY) {  //原HashMap的越界检查

            threshold = Integer.MAX_VALUE;

            return oldTab;

        }

        else if ((newCap = oldCap <<1) < MAXIMUM_CAPACITY &&  oldCap >=DEFAULT_INITIAL_CAPACITY)    //容量扩大一倍后的越界检查

            newThr = oldThr <<1;// double threshold

    }

        //case2:oldCap=0 && oldThr >0,桶数组尚未初始化,当调用带初始化容量的构造函数时会发生该情况

    else if (oldThr >0)// initial capacity was placed in threshold  

        newCap = oldThr;      //在前面HashMap的初始化中,将Initial capcity暂存在threshold中,新的阀值按照下面newThr==0中的公式进行计算

        //case3:oldCap=0 && oldThr = 0,当调用无参构造函数时会发生该情况,此时使用默认容量初始化

    else {// zero initial threshold signifies using defaults

        newCap =DEFAULT_INITIAL_CAPACITY;  //默认容量

        newThr = (int)(DEFAULT_LOAD_FACTOR *DEFAULT_INITIAL_CAPACITY);  //默认扩容临界值

    }

    if (newThr ==0) {    //case2中,调用Initial capcity构造方法时,该阀值为0,需计算阀值

        float ft = (float)newCap *loadFactor;

        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 35(int)ft : Integer.MAX_VALUE);

    }

    threshold = newThr;

    @SuppressWarnings({"rawtypes","unchecked"})

        Node[] newTab = (Node[])new Node[newCap];  //上面获取到的新的Capcity,来创建一个新的桶数组 newTab,并指向table

    table = newTab;

    if (oldTab !=null) {    //若oldTab非空,则需要将原来桶数组的元素取出来放到新的桶数组中

        for (int j =0; j < oldCap; ++j) {

            Node e;

            if ((e = oldTab[j]) !=null) {

                oldTab[j] =null;    //将原桶数组的元素占用的空间释放,便于GC

                if (e.next ==null)   //若桶中元素的next为空,获取index后直接将其放入新桶数组中

                    newTab[e.hash & (newCap -1)] = e;

                else if (einstanceof TreeNode)   //若桶中元素的next是树节点

                    ((TreeNode)e).split(this, newTab, j, oldCap);    //采用树的方式插入

                else {// preserve order          若桶中元素的next是链表节点

                    Node loHead =null, loTail =null;

                    Node hiHead =null, hiTail =null;

                    Node next;

                    do {

                          next = e.next;

                          if ((e.hash & oldCap) ==0) {    //若e.e.hash & oldCap 结果为0,则下标在新桶数组中不用改变,此时,将元素存放在loHead为首的链表中

                               if (loTail ==null)

                                    loHead = e;

                               else

                                    loTail.next = e;

                                loTail = e;

                            }

                           else {                        //若e.e.hash & oldCap 结果不为0,则下标在新桶数组等于原下标+oldCap,此时,将元素存放在hiHead为首的链表中

                                 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;

}

/*原始链表中的元素,在resize之后,其下标有两种可能,一种是在原来下标处,另一种是原来下标+oldCap处 

 *举例说明: 若原来的容量 -1后 只有n位,低位有n个1,去下标公式为:i = (oldCap - 1) & hash,若hash值只有低n为有值,则与运算后获得的值和 

 *扩容前是一样的,若hash不止第n位有值,那采用与运算后,结果比原来刚好大oldCap。 下面有图片示例) 

*/

上述代码分析较长,总结如下:

1.获取不同情况下的 新的容量 和 新的扩容临界值

2.根据新容量创建新的桶数组tab。

3.根据节点类型,树节点和链表节点分别采用对应方法放入新的桶数组

7)查找元素方法 getNode

final Node getNode(int hash, Object key) {

    Node[] tab; Node first, e;int n;K k;

    if ((tab =table) !=null && (n = tab.length) >0 && (first = tab[(n -1) & hash]) !=null) {    //根据hash值,获取对应下标的第一个元素first

        if (first.hash == hash && ((k = first.key) == key || (key !=null && key.equals(k))))// always check first node  如果first的key和待查询的key相等,返回first

            return first;

        if ((e = first.next) !=null) {   //若first不是待查询的元素

            if (firstinstanceof TreeNode)    //若first是树节点,采用树节点的方式获取

                return ((TreeNode)first).getTreeNode(hash, key);

            do {

                if (e.hash == hash && ((k = e.key) == key || (key !=null && key.equals(k))))   //first是链表节点头,使用循环获取

                    return e;

            }while ((e = e.next) !=null);

        }

    }

    return null;

}

查询元素的入口方法是:public V get(Object key),返回值是node的value,核心方法是getNode(int hash, Object key)。

8)删除元素 removeNode方法

final Node removeNode(int hash, Object key, Object value, boolean matchValue,boolean movable) {

    Node[] tab; Node p;int n, index;

    //通过hash值获取下标,下标对应的节点p不为空

    if ((tab =table) !=null && (n = tab.length) >0 && (p = tab[index = (n -1) & hash]) !=null) { 

        Node node =null, e;K k;V v;

        if (p.hash == hash && ((k = p.key) == key || (key !=null && key.equals(k))))    //若节点p的key和待移除的节点key相等

            node = p;   //将p指向待移除节点

        else if ((e = p.next) !=null) {                                                               //p的key和待移除的节点key不相等,遍历p作为头的链表或者树

            if (pinstanceof TreeNode)               //采用树节点方式获得要移除的节点

                node = ((TreeNode)p).getTreeNode(hash, key);

            else {                                             //p是链表的头节点

                do {

                    //采用循环,当p.next不为空,比对它和传入的key,直到找到相等的key

                    if (e.hash == hash && ((k = e.key) == key || (key !=null && key.equals(k)))) {

                        node = e;   //找到后,将节点指向node

                        break;       //将e指向待移除节点,此时相当于p.next就是待移除的节点node

                    }

                    p = e;

                }while ((e = e.next) !=null);

            }

        }

        //若node非空,传入的matchValue参数为flase或 node的value等于传入value

        if (node !=null && (!matchValue || (v = node.value) == value || (value !=null && value.equals(v)))) {

            if (nodeinstanceof TreeNode)      //若node是树节点

                ((TreeNode)node).removeTreeNode(this, tab, movable);

            else if (node == p)      //若待移除节点是链表头,将其指向待移除元素的next,移除对node的引用

                tab[index] = node.next;

            else                           //待移除元素是链表中的元素,此时其等于p.next

                p.next = node.next;             //将p.next指向node.next,移除了对node的引用

            ++modCount; 

            --size;

            afterNodeRemoval(node);

            return node;

        }

    }

    return null;

}

移除节点的入口方法是: public V remove(Object key)  ,其核心方法是removeNode,主要做了以下几个工作:

1.通过用key获取的hash,来获取下标。

2.若下标对应处无元素,返回null。

3.若下标对应处有元素,判断是树或者链表,采用对应方法移除。

3.ConcurrentHashMap

1.来源

自jdk1.5,继承了AbstractMap类并实现了ConcurrentMap接口

2.字段说明

private static final int MAXIMUM_CAPACITY = 1 << 30; 最大容量
private static final int DEFAULT_CAPACITY = 16; 默认容量
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;  最大数组的size
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;   默认的同步级别
private static final float LOAD_FACTOR = 0.75f;  默认加载因子
private static final int MIN_TRANSFER_STRIDE = 16;   
private static int RESIZE_STAMP_BITS = 16;
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

3.主要方法

1)核心构造方法

    public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        //保证初始容量不小于同步级别
        if (initialCapacity < concurrencyLevel)   // Use at least as many bins
            initialCapacity = concurrencyLevel;   // as estimated threads
        long size = (long)(1.0 + (long)initialCapacity / loadFactor);
        //计算容量
        int cap = (size >= (long)MAXIMUM_CAPACITY) ?
            MAXIMUM_CAPACITY : tableSizeFor((int)size);
        this.sizeCtl = cap;
    }

1.该构造方法包含了三个参数,初始容量initialCapacity,加载因子loadFactor,同步级别concurrencyLevel

2.初始容量不能小于同步级别,若小于将初始容量设置为同步界别的值

3.计算容量时的tableSizeFor方法与HashMap中一致

4.sizeCtl == cap容量

2)其他构造方法

    /**
     * Creates a new, empty map with the default initial table size (16).
     */
    //无参构造方法
    public ConcurrentHashMap() {
    }

    //指定容量构造方法
    public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        this.sizeCtl = cap;
    }

    //指定容量和加载因子构造方法
    public ConcurrentHashMap(int initialCapacity, float loadFactor) {
        this(initialCapacity, loadFactor, 1);
    }

3)put方法

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            //判断table的i节点是否为null
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                    //使用CAS方式放入该值
                    if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            //
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            //进入具体插入逻辑
            else {
                V oldVal = null;
                //对数组对应节点及该节点下的所有节点进行同步访问
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        fh>0 说明这个节点是一个链表的节点 不是树的节点
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //如果hash值和key值相同  则修改对应结点的value值
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                //如果遍历到了最后一个结点,那么就证明新的节点需要插入 就把它                                                                
                                   插入在链表尾部
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        //如果这个节点是树节点,就按照树的方式插入值
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    //如果链表长度已经达到临界值8 就需要把链表转换为树结构
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        //将当前ConcurrentHashMap的元素数量+1
        addCount(1L, binCount);
        return null;
    }

put方法最后调用的都是putVal方法,主要有一下几点需要注意

1.不允许key和value为null

2.当该key计算出数组中index的下标为空时,使用了CAS(compareAndSwap)来放入该Node节点。放入成功直接返回,否则进入下个else if判断。

3.对key对应数组下标的第一个节点进行同步访问(synchronized)

4)get方法

    public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        //计算hash值        
        int h = spread(key.hashCode());
        //根据hash值确定节点位置
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            //如果搜索到的节点key与传入的key相同且不为null,直接返回这个节点
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            //如果eh<0 说明这个节点在树上 直接寻找
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            //否则遍历链表 找到对应的值并返回
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

get方法比较简单,给定一个key来确定value的时候,必须满足两个条件  key相同  hash值相同,对于节点可能在链表或树上的情况,需要分别去查找.

5)replaceNode方法

    //remove方法
    public V remove(Object key) {
        return replaceNode(key, null, null);
    }

    //真正删除节点的方法
    final V replaceNode(Object key, V value, Object cv) {
        //计算hash值
        int hash = spread(key.hashCode());
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //null,直接break
            if (tab == null || (n = tab.length) == 0 ||
                (f = tabAt(tab, i = (n - 1) & hash)) == null)
                break;
            //如果当前tab在扩容中,参加到扩容操作中
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            //否则去删除元素
            else {
                V oldVal = null;
                boolean validated = false;
                //对f节点加锁,进行同步访问
                synchronized (f) {
                    fh>0 说明这个节点是一个链表的节点 不是树的节点
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            validated = true;
                            for (Node<K,V> e = f, pred = null;;) {
                                K ek;
                                //找到节点删除节点
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    V ev = e.val;
                                    if (cv == null || cv == ev ||
                                        (ev != null && cv.equals(ev))) {
                                        oldVal = ev;
                                        if (value != null)
                                            e.val = value;
                                        else if (pred != null)
                                            pred.next = e.next;
                                        else
                                            setTabAt(tab, i, e.next);
                                    }
                                    break;
                                }
                                pred = e;
                                if ((e = e.next) == null)
                                    break;
                            }
                        }
                        //如果是树节点则进行树节点的删除
                        else if (f instanceof TreeBin) {
                            validated = true;
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> r, p;
                            if ((r = t.root) != null &&
                                (p = r.findTreeNode(hash, key, null)) != null) {
                                V pv = p.val;
                                if (cv == null || cv == pv ||
                                    (pv != null && cv.equals(pv))) {
                                    oldVal = pv;
                                    if (value != null)
                                        p.val = value;
                                    else if (t.removeTreeNode(p))
                                        setTabAt(tab, i, untreeify(t.first));
                                }
                            }
                        }
                    }
                }
                //成功删除,count-1,返回oldVal
                if (validated) {
                    if (oldVal != null) {
                        if (value == null)
                            addCount(-1L, -1);
                        return oldVal;
                    }
                    break;
                }
            }
        }
        return null;
    }

我们可以看到remove方法最后会调用到replaceNode方法,这个方法在执行删除操作时,对数组的下标元素进行了加锁保证,对该节点的链表或树进行同步访问,保证了线程安全

6)keySet方法

    public KeySetView<K,V> keySet() {
        KeySetView<K,V> ks;
        //传入ConcurrentHashMap自己本身
        return (ks = keySet) != null ? ks : (keySet = new KeySetView<K,V>(this, null));
    }
    

    public static class KeySetView<K,V> extends CollectionView<K,V,K>
        implements Set<K>, java.io.Serializable {
        private static final long serialVersionUID = 7249069246763182397L;
        private final V value;
        //构造方法,获取传入的ConcurrentHashMap
        KeySetView(ConcurrentHashMap<K,V> map, V value) {  // non-public
            super(map);
            this.value = value;
        }

        /**
         * Returns the default mapped value for additions,
         * or {@code null} if additions are not supported.
         *
         * @return the default mapped value for additions, or {@code null}
         * if not supported
         */
       //返回传入ConcurrentHashMap的value
        public V getMappedValue() { return value; }

        /**
         * {@inheritDoc}
         * @throws NullPointerException if the specified key is null
         */
        //调用传入ConcurrentHashMap的containsKey方法
        public boolean contains(Object o) { return map.containsKey(o); }

        /**
         * Removes the key from this map view, by removing the key (and its
         * corresponding value) from the backing map.  This method does
         * nothing if the key is not in the map.
         *
         * @param  o the key to be removed from the backing map
         * @return {@code true} if the backing map contained the specified key
         * @throws NullPointerException if the specified key is null
         */
        //调用传入ConcurrentHashMap的remove方法
        public boolean remove(Object o) { return map.remove(o) != null; }

        /**
         * @return an iterator over the keys of the backing map
         */
        //对传入的map,创建一个Key的迭代器
        public Iterator<K> iterator() {
            Node<K,V>[] t;
            ConcurrentHashMap<K,V> m = map;
            int f = (t = m.table) == null ? 0 : t.length;
            return new KeyIterator<K,V>(t, f, 0, f, m);
        }

        /**
         * Adds the specified key to this set view by mapping the key to
         * the default mapped value in the backing map, if defined.
         *
         * @param e key to be added
         * @return {@code true} if this set changed as a result of the call
         * @throws NullPointerException if the specified key is null
         * @throws UnsupportedOperationException if no default mapped value
         * for additions was provided
         */
        //add方法也是调用了传入ConcurrentHashMap的putVal方法
        public boolean add(K e) {
            V v;
            if ((v = value) == null)
                throw new UnsupportedOperationException();
            return map.putVal(e, v, true) == null;
        }

        /**
         * Adds all of the elements in the specified collection to this set,
         * as if by calling {@link #add} on each one.
         *
         * @param c the elements to be inserted into this set
         * @return {@code true} if this set changed as a result of the call
         * @throws NullPointerException if the collection or any of its
         * elements are {@code null}
         * @throws UnsupportedOperationException if no default mapped value
         * for additions was provided
         */
        public boolean addAll(Collection<? extends K> c) {
            boolean added = false;
            V v;
            if ((v = value) == null)
                throw new UnsupportedOperationException();
            for (K e : c) {
                if (map.putVal(e, v, true) == null)
                    added = true;
            }
            return added;
        }

        public int hashCode() {
            int h = 0;
            for (K e : this)
                h += e.hashCode();
            return h;
        }

        public boolean equals(Object o) {
            Set<?> c;
            return ((o instanceof Set) &&
                    ((c = (Set<?>)o) == this ||
                     (containsAll(c) && c.containsAll(this))));
        }

        public Spliterator<K> spliterator() {
            Node<K,V>[] t;
            ConcurrentHashMap<K,V> m = map;
            long n = m.sumCount();
            int f = (t = m.table) == null ? 0 : t.length;
            return new KeySpliterator<K,V>(t, f, 0, f, n < 0L ? 0L : n);
        }

        public void forEach(Consumer<? super K> action) {
            if (action == null) throw new NullPointerException();
            Node<K,V>[] t;
            if ((t = map.table) != null) {
                Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
                for (Node<K,V> p; (p = it.advance()) != null; )
                    action.accept(p.key);
            }
        }
    }

keySet方法中其核心就是调用了其静态内部类KeySetView<K,V>,其大部分方法都是调用了传入的ConcurrentHashMap本身的field或者method。另外一些method进行了自己的处理。

4.三个类的对比

1.线程安全性

从源码看,HashTable和ConcurrentHashMap是线程安全的,而HashMap是非线程安全的。HashTable与ConcurrentHashMap最本质的区别就是性能问题。从源码我们分析出,HastTable是对整个table进行加锁,这样在多线程访问时,只允许单线程操作;而ConcurrentHashMap则是依据每个table中的节点进行加锁,这样在不发生冲突的理想情况下,是可以同时保证table.length()多个线程进行操作。所以推荐用ConcurrentHashMap。至于HashMap,没有考虑多线程操作这种情况,至于不安全的地方网上一搜一大把。

2.key value值问题

Hashtable中key和value都不能为null,HashMap中, key可以为null,但这样的健值只有一个,ConcurrentHashMap中,key和value都不允许空值

3.遍历方式

ConcurrentHashMap、HashMap使用了Iterator, Hashtable使用了Enumeration的方式

4.扩容方式

HashTable中hash数组默认大小为11, 增加的方式是old*2+1,  ConcurrentHashMap、HashMap数组的默认大小是16, 而且一定是2的指数,扩容也是原来的2倍

 

 

码字不易,有不对的地方欢迎指正。这只是初步的分析,对于ConcurrentHashMap这个类的精髓还需要多读才能get到。

 

 

 

 

 

 

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值