Skip to main content Link Menu Expand (external link) Document Search Copy Copied

1. Map

  可为空 有序 线程安全 其他
HashMap  
HashTable 效率慢
TreeMap 红黑树
LinkedHashMap 先后顺序 红黑树
ConcurrentHashMap    

2. HashMap

transient Node<K, V>[]table; // 不能被序列化
static final int DEFAULT_INITIAL_CAPACITY=1<<4; // aka 16 初始大小为16
static final int MAXIMUM_CAPACITY=1<<30;
//负载因子当map中的个数超过capacity*load factor时,
// 重新调整capacity大小为当前的2倍,
// 又称作装填因子,值越大,产生冲突的可能就越大。
static final float DEFAULT_LOAD_FACTOR=0.75f;

static final int hash(Object key){
        int h;
        //这么的原因是混合原始哈希码的高位和低位,以此来加大低位的随机性,
        // 而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。
        //还有一个数组的长度不可能达到2^32,所以简单的将hash值与数据长度相与,
        // hash值的高位特征一址没有被使用,所以把16位右移,
        // 与低16异或,加大了随机性,也利用的高位的值特征
        return(key==null)?0:(h=key.hashCode())^(h>>>16);//无符号右移
        }


final V putVal(int hash,K key,V value,boolean onlyIfAbsent,
        boolean evict){
        Node<K, V>[]tab;Node<K, V> p;int n,i;
        if((tab=table)==null||(n=tab.length)==0)
        n=(tab=resize()).length;
        if((p=tab[i=(n-1)&hash])==null)//将hash值与数组长度相与,得了的索引值不冲突,直接存入
        tab[i]=newNode(hash,key,value,null);
        else{//冲突了
        Node<K, V> e;K k;
        //hash值相同,键值也相同
        if(p.hash==hash&&
        ((k=p.key)==key||(key!=null&&key.equals(k))))
        e=p;//e暂存这个已存在的冲突元素
        //hash值相同,键值不同,但这个已存在元素是以TreeNode形式存储
        else if(p instanceof TreeNode)
        e=((TreeNode<K, V>)p).putTreeVal(this,tab,hash,key,value);
        //碰撞发生,发链表形式存放在末尾
        else{
        for(int binCount=0;;++binCount){
        if((e=p.next)==null){
        p.next=newNode(hash,key,value,null);
        if(binCount>=TREEIFY_THRESHOLD-1) // -1 for 1st
        treeifyBin(tab,hash);
        break;
        }
        if(e.hash==hash&&
        ((k=e.key)==key||(key!=null&&key.equals(k))))
        break;
        p=e;
        }
        }
        //对象存在,更新值
        if(e!=null){ // existing mapping for key
        V oldValue=e.value;
        if(!onlyIfAbsent||oldValue==null)
        e.value=value;
        afterNodeAccess(e);
        return oldValue;
        }
        }
        ++modCount;
        //大小越界,重新调整
        if(++size>threshold)
        resize();
        afterNodeInsertion(evict);
        return null;
        }


final Node<K, V> getNode(int hash,Object key){
        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;
        }
  • 使用头插法替换尾插法,避免出现循环列表
  • 当长度达到8时,链表转换成红黑树
  • 扩容时是原来的2倍,在扩容时可以确保扩容前的链表结构不变,key冲突的在新的扩容桶中

3. LinkedHashMap

可用于LRU(最近最少使用)算法的实现,还有一种实现方式就是MAP加上LinkedList(双向链表)。

4. ConcurrentHashMap

4.1. ConcurrentHashMap 是如何保证线程安全的?

通过使用分段锁(在 Java 7 及之前的版本中)和 CAS(Compare-And-Swap)操作(在 Java 8 及之后的版本中)来实现线程安全。 在 Java 7 及之前的版本中,ConcurrentHashMap 使用分段锁来提高并发性能。 每个 segment 是一个独立的哈希表,拥有自己的锁,允许多个线程同时读写不同的 segment。 在 Java 8 中,ConcurrentHashMap 的实现发生了重大变化,放弃了分段锁的设计, 采用了基于 Node 数组 + CAS + Synchronized 的方式来实现线程安全。 这种新的设计在减少内存消耗和提高并发性能方面有所优化。

4.2. 与 Hashtable 和 Collections.synchronizedMap() 相比,ConcurrentHashMap 有哪些优势?

Hashtable 在整个表上加锁,影响并发性能;Collections.synchronizedMap() 返回的 Map 也是线程安全的, 但它也是在整个表上加锁。而 ConcurrentHashMap 通过分段锁或 CAS 操作实现了更细粒度的锁控制,提高了并发性能。