Java集合总结Map

常用的Java集合有两部分组成Map与Collection

简介

  • Collection
  • Map

Map

  • HashMap
      hashMap既具有ArrayList的查询快,也具有LinkedList的增删快。其实将ArrayList(也叫哈希表)与LinkedList组合起来,就是我们经常使用的HashMap。
    HashMap的数据结构
    在这里插入图片描述
    从源码中分析可知,初始化的时候这个ArrayList的长度为16。存放之后,肯定会出现键值对冲突问题。HashMap是如何解决键值对的冲突问题呢?
     hashMap中引入了hashcode(),作为ArrayList的键值。
  • 当要存放的ArrayList判断hash后为空的时候,就直接存放到ArrayList中
  • 当要存放的ArrayList判断hash后不为空的时候,就在当前ArrayList的下标下,接上了一个单链表。

在1.8之前,新插入的元素都是放在了链表的头部位置,但是这种操作在高并发的环境下容易导致死锁,所以1.8之后,新插入的元素都放在了链表的尾部。

上源码

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    //初始化的时候ArrayList的长度
	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
	//ArrayList最大可扩容长度
	static final int MAXIMUM_CAPACITY = 1 << 30;
	//加载因子  也就是说,当ArrayList存放数量达到了3/4,就会动态扩容ArrayList
	static final float DEFAULT_LOAD_FACTOR = 0.75f;
	//每个ArrayList的下标允许的最大单链表长度。一旦超过,单链表就换转化为红黑树
	static final int TREEIFY_THRESHOLD = 8;
//Map中包含的元素数量
    transient int size;

    //阈值,用于判断是否需要扩容(threshold = 容量*负载因子)
    int threshold;

    //加载因子实际的大小
    final float loadFactor;

	//链表扩容的时候,每个节点
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;		//确定每个节点的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;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }
    
    //hashMap的put方法
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

	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;  
     /*如果table的在(n-1)&hash的值是空,就新建一个节点插入在该位置*/  
        if ((p = tab[i = (n - 1) & hash]) == null)  
             tab[i] = newNode(hash, key, value, null);  
     /*表示有冲突,开始处理冲突*/  
        else {  
             Node<K,V> e;   
         K k;  
     /*检查第一个Node,p是不是要找的值*/  
             if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))  
                 e = p;  
             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);  
                //如果冲突的节点数已经达到8个,看是否需要改变冲突节点的存储结构,               
             //treeifyBin首先判断当前hashMap的长度,如果不足64,只进行  
                         //resize,扩容table,如果达到64,那么将冲突的存储结构为红黑树  
                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st  
                             treeifyBin(tab, hash);  
                         break;  
                     }  
         /*如果有相同的key值就结束遍历*/  
                     if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))  
                         break;  
                     p = e;  
                 }  
             }  
     /*就是链表上有相同的key值*/  
             if (e != null) { // existing mapping for key,就是key的Value存在  
                 V oldValue = e.value;  
                 if (!onlyIfAbsent || oldValue == null)  
                     e.value = value;  
                 afterNodeAccess(e);  
                 return oldValue;//返回存在的Value值  
             }  
         }  
         ++modCount;  
      /*如果当前大小大于门限,门限原本是初始容量*0.75*/  
         if (++size > threshold)  
             resize();//扩容两倍  
         afterNodeInsertion(evict);  
         return null;  
     }  
}

大致执行流程图
在这里插入图片描述
 总的来说HashMap是以牺牲内存空间为代价,换取的高速的查询与插入删除操作的。
 HashMap有且允许一个主键(key)的值为Null,value无限制。当出现多个主键为Null的值插入的时候,保留最后一个插入的元素的值。
2. HashTable
  HashTable的实现原理几乎与HashMap类似。但是不同的是,hashTable中的方法。大部分了加了synchronized关键字。也就是线程同步的。并且,hashTable中只是使用了链表来解决hash冲突的问题,不会演化为红黑树。

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {
    /**
     * The hash table data.
     * ArrayList结构式的哈希表
     */
    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;
	
	//hashtable新增的方法
	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];
        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;
        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++;
    }

 HashTable不允许主键(key)与值(value)为空的情况。若出现了主键为空的情况。编译可以通过,但是运行的时候会出现空指针异常。

  1. HashSet
     HashSet的添加方法使用的是add()方法,但是我们debug可以发现。其实hashSet使用的就是HashMap的put方法进行新增。并且将新增的值放入到key当中,不设置value的值。
    hashSet继承hashMap的加载因子0.75也就是3/4,初始容量也为16。扩容机制也与HashMap完全一样。

Map集合探究到此为止,本想继续深究LinkedHashMap与LinkedHashSet。但是Java的与Map有关的方法,被ExpiringCacheache(java.io包下的)修饰了,第二次往map相关的子类中存放东西,优先查询这个缓存类。导致的调试起来困难。

发布了16 篇原创文章 · 获赞 1 · 访问量 868

猜你喜欢

转载自blog.csdn.net/nisemono_ct/article/details/103070534