java集合-HashMap(JDK1.8)

来源:互联网 发布:信誉出肉 淘宝 编辑:程序博客网 时间:2024/06/07 03:09

一、基本概念

HashMap基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。以前JDK中HashMap采用的是位桶+链表的方式,即我们常说的散列链表的方式,而JDK1.8中采用的是位桶+链表/红黑树的方式,也是非线程安全的。当某个位桶的链表的长度达到某个阀值的时候,这个链表就将转换成红黑树。
注意事项:

  1. HashMap 是一个散列表,它存储的内容是键值对(key-value)映射;
  2. HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口;
  3. HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null;
  4. HashMap中的映射不是有序的;
  5. HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”;

二、源码分析

1:常量

   /**   map的最大容量     */    static final int MAXIMUM_CAPACITY = 1 << 30;    /**     * 默认加载因子     */    static final float DEFAULT_LOAD_FACTOR = 0.75f;    /**     将list链表转为红黑数阀值,即list的size超过时转化     */    static final int TREEIFY_THRESHOLD = 8;    /**     resize操作中,决定是否untreeify的阈值     */    static final int UNTREEIFY_THRESHOLD = 6;    /**     决定是否转换成tree的最小容量     */    static final int MIN_TREEIFY_CAPACITY = 64;

2:主要字段

/**存储元素的数组*/ transient Node<K,V>[] table; /** 用于map迭代遍历 */ transient Set<Map.Entry<K,V>> entrySet;    /**     元素个数     */    transient int size;    /**     修改次数     */    transient int modCount;    /**    阀值,用于扩容阀值     */    int threshold;    /**     加载因子     */    final float loadFactor;

3:主要方法
get方法:

public V get(Object key) {        Node<K,V> e;        return (e = getNode(hash(key), key)) == null ? null : e.value;    }    /**     * Implements Map.get and related methods     *     * @param hash hash for key     * @param key the key     * @return the node, or null if none     */    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 && // 总是判断第一个元素是否满足条件                ((k = first.key) == key || (key != null && key.equals(k))))                return first;            if ((e = first.next) != null) {            //第一个节点为TreeNode,则调用TreeNode.getTreeNode()方法遍历红黑数进行查询                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;    }

put方法

public V put(K key, V value) {        return putVal(hash(key), key, value, false, true);    }    /**     * Implements Map.put and related methods     *     * @param hash hash for key     * @param key the key     * @param value the value to put     * @param onlyIfAbsent if true, don't change existing value     * @param evict if false, the table is in creation mode.     * @return previous value, or null if none     */    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)        //table为空,n为table的长度            n = (tab = resize()).length;            //i位置为空,直接存储        if ((p = tab[i = (n - 1) & hash]) == null)            tab[i] = newNode(hash, key, value, null);        else {        // 若i位置上的值不为空,判断当前位置上的Node p 是否与要插入的key的hash和key相同            Node<K,V> e; K k;            if (p.hash == hash &&                ((k = p.key) == key || (key != null && key.equals(k))))                //相同key时直接覆盖                e = p;            else if (p instanceof TreeNode)            //不相同时,若当前p已经为TreeNode,则插入该树上                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);            else {            //在i位置上的链表中找到p.next为null的位置,binCount计算出当前链表的长度,如果继续将冲突的节点插入到该链表中,会使链表的长度大于tree化的阈值,则将链表转换成tree。                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;    }

resize(),由于解决冲突的方法可能是list,也可能是红黑数,所以resize()较为复杂点。

/**     * Initializes or doubles table size.  If null, allocates in     * accord with initial capacity target held in field threshold.     * Otherwise, because we are using power-of-two expansion, the     * elements from each bin must either stay at same index, or move     * with a power of two offset in the new table.     *     * @return the table     */    final Node<K,V>[] resize() {        Node<K,V>[] oldTab = table;        int oldCap = (oldTab == null) ? 0 : oldTab.length;        int oldThr = threshold;        int newCap, newThr = 0;        if (oldCap > 0) {        //若超过最大容量,则不能扩容            if (oldCap >= MAXIMUM_CAPACITY) {                threshold = Integer.MAX_VALUE;                return oldTab;            }            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&                     oldCap >= DEFAULT_INITIAL_CAPACITY)                newThr = oldThr << 1; //阀值扩大2倍         }        else if (oldThr > 0) // initial capacity was placed in threshold            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"})        // 创建容量为newCap的newTab,并将oldTab中的Node迁移过来,这里需要考虑链表和tree两种情况。            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];        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)                    // split方法会将树分割为lower 和upper tree两个树,如果子树的节点数小于了UNTREEIFY_THRESHOLD阈值,则将树untreeify,将节点都存放在newTab中。                        ((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;    }

remove()方法

public V remove(Object key) {        Node<K,V> e;        return (e = removeNode(hash(key), key, null, false, true)) == null ?            null : e.value;    }    /**     * Implements Map.remove and related methods     *     * @param hash hash for key     * @param key the key     * @param value the value to match if matchValue, else ignored     * @param matchValue if true only remove if value is equal     * @param movable if false do not move other nodes while removing     * @return the node, or null if none     */    final Node<K,V> removeNode(int hash, Object key, Object value,                               boolean matchValue, boolean movable) {        Node<K,V>[] tab; Node<K,V> p; int n, index;        if ((tab = table) != null && (n = tab.length) > 0 &&            (p = tab[index = (n - 1) & hash]) != null) {            Node<K,V> node = null, e; K k; V v;            if (p.hash == hash &&                ((k = p.key) == key || (key != null && key.equals(k))))                node = p;                //若是用红黑数解决冲突则getTreeNode方法查找到节点            else if ((e = p.next) != null) {                if (p instanceof TreeNode)                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);                else {                    do {                        if (e.hash == hash &&                            ((k = e.key) == key ||                             (key != null && key.equals(k)))) {                            node = e;                            break;                        }                        p = e;                    } while ((e = e.next) != null);                }            }            if (node != null && (!matchValue || (v = node.value) == value ||                                 (value != null && value.equals(v)))) {                if (node instanceof TreeNode)                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);                else if (node == p)                    tab[index] = node.next;                else                    p.next = node.next;                ++modCount;                --size;                afterNodeRemoval(node);                return node;            }        }        return null;    }

HashMap实例

1:Hashmap的遍历方法

package com.csu.collection;import java.util.HashMap;import java.util.Iterator;import java.util.Map.Entry;import java.util.Set;public class HashMapTest {    public static void main(String[]args)    {        HashMap<Integer, Integer> map=new HashMap<>();        for(int i=0;i<10000000;i++)        {            map.put(i, i);        }        System.out.println("第一种遍历方法:for each map.entrySet()");        long startTime1=System.currentTimeMillis();        for(Entry<Integer, Integer> entry:map.entrySet())        {            entry.getValue();            entry.getKey();        }        long endTime1=System.currentTimeMillis();        System.out.println("第一种遍历方法用时:"+(endTime1-startTime1)+"ms");        System.out.println("第2种遍历方法:map.entrySet()的集合迭代器");        long startTime2=System.currentTimeMillis();        Iterator<Entry<Integer, Integer>> iterator=map.entrySet().iterator();        while(iterator.hasNext())        {            HashMap.Entry<Integer, Integer> entry=(Entry<Integer, Integer>) iterator.next();            entry.getValue();            entry.getKey();        }        long endTime2=System.currentTimeMillis();        System.out.println("第2种遍历方法用时:"+(endTime2-startTime2)+"ms");        System.out.println("第3种遍历方法: for each map.keySet(),再调用get获取");        long startTime3=System.currentTimeMillis();        for (Integer key : map.keySet()) {            map.get(key);        }        long endTime3=System.currentTimeMillis();        System.out.println("第3种遍历方法用时:"+(endTime3-startTime3)+"ms");        System.out.println("第4种遍历方法:for each map.entrySet(),用临时变量保存map.entrySet()");        long startTime4=System.currentTimeMillis();        Set<Entry<Integer, Integer>> entrySet = map.entrySet();        for (Entry<Integer, Integer> entry : entrySet) {            entry.getKey();            entry.getValue();        }        long endTime4=System.currentTimeMillis();        System.out.println("第4种遍历方法用时:"+(endTime4-startTime4)+"ms");    }}

运行结果

第一种遍历方法:for each map.entrySet()第一种遍历方法用时:71ms第2种遍历方法:map.entrySet()的集合迭代器第2种遍历方法用时:83ms第3种遍历方法: for each map.keySet(),再调用get获取第3种遍历方法用时:117ms第4种遍历方法:for each map.entrySet(),用临时变量保存map.entrySet()第4种遍历方法用时:84ms

总结:

  1. a. HashMap的循环,如果既需要key也需要value,直接用for each map.entrySet();
  2. 如果只是遍历key而无需value的话,可以直接用for each map.keySet(),再调用get获取。
    2:使用Hashmap 实现缓存
public class Student {    private String name;    private String address;    public Student(String name,String address)    {        this.address=address;        this.name=name;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String getAddress() {        return address;    }    public void setAddress(String address) {        this.address = address;    }}
import java.io.Serializable;public class CacheEntity implements Serializable {    /**     *      */    private static final long serialVersionUID = 1L;    private final int DEFUALT_TIME=200;//秒    private String  key;    private Object value;    private int time;//缓存存活时间,不设置则使用默认值    private long timeoutStamp;// 缓存过期时间戳    @SuppressWarnings("unused")    private CacheEntity()    {        this.timeoutStamp=System.currentTimeMillis()+DEFUALT_TIME*1000;        this.time=DEFUALT_TIME;    }    public CacheEntity(String key,Object value)    {        this.key=key;        this.value=value;    }    public CacheEntity(String key,Object value,long timestamp)    {        this(key,value);        this.timeoutStamp=timestamp;    }    public CacheEntity(String key,Object value,int time)    {        this(key,value);        this.time=time;        this.timeoutStamp=System.currentTimeMillis()+DEFUALT_TIME*1000;    }    public String getKey() {        return key;    }    public void setKey(String key) {        this.key = key;    }    public Object getValue() {        return value;    }    public void setValue(Object value) {        this.value = value;    }    public int getTime() {        return time;    }    public void setTime(int time) {        this.time = time;    }    public long getTimeoutStamp() {        return timeoutStamp;    }    public void setTimeoutStamp(long timeoutStamp) {        this.timeoutStamp = timeoutStamp;    }}
import java.util.ArrayList;import java.util.HashMap;import java.util.List;/** *  * 采用队列,定时循环清理过期缓存 * */public class CacheByHashMap {    private static  HashMap<String, CacheEntity> map;    private static List<CacheEntity> tempList;    static{        tempList=new ArrayList<CacheEntity>();        map=new HashMap<String,CacheEntity>(1<<10);         new Thread(new RemoveTimeOutCacheThread()).start();    }  /**   * 添加缓存   * @param key   * @param value   * @param time   */    public static synchronized void addCache(String key,CacheEntity value,int time)    {        value.setTimeoutStamp(System.currentTimeMillis()+time*1000);        map.put(key, value);        tempList.add(value);    }    /**     * 获取缓存对象     * @param key     * @return     */    public static synchronized CacheEntity getCache(String key)    {        return map.get(key);    }    /**     * 检查是否包含特定的key     * @param key     * @return     */    public static synchronized boolean isContainsKey(String key)    {        return map.containsKey(key);    }    /**     * 删除缓存     * @param key     */    public static synchronized void removeCache(String key)    {        map.remove(key);    }    /**     *  获取缓存数量     * @return     */    public static int getCacheSize()    {      return  map.size();    }    /**     * 清除所有缓存     */    public static synchronized void clearCache()    {        tempList.clear();        map.clear();        System.out.println("所有缓存被清理");    }    static class RemoveTimeOutCacheThread implements Runnable{        @Override        public void run() {            // TODO Auto-generated method stub            while(true)            {                try {                    checkTime();                } catch (Exception e) {                    // TODO: handle exception                    e.printStackTrace();                }            }        }        private void checkTime() throws InterruptedException        {            CacheEntity value=null;            long timeoutTime=1000l;            if(tempList.size()<1)            {                System.out.println("过期队列为空!");                timeoutTime=1000l;                Thread.sleep(timeoutTime);                return ;            }            value=tempList.get(0);            timeoutTime=value.getTimeoutStamp()-System.currentTimeMillis();            if(timeoutTime>0)            {                Thread.sleep(timeoutTime);                return ;            }            System.out.println("清除过期缓存"+value.getKey());            tempList.remove(value);            removeCache(value.getKey());        }    }}

测试端代码:

public class CacheTest {    public static void main(String[] args) {        // TODO Auto-generated method stub        Student student1=new Student("zhangsan", "shangsha");        Student student2=new Student("wangqiang", "beijing");        Student student3=new Student("zhangsi", "shanghai");        Student student4=new Student("zhangwu", "wuhan");        Student student5=new Student("zhangqi", "zhengzhou");        Student student6=new Student("zhangba", "shangsha");        CacheEntity cacheEntity1=new CacheEntity("1", student1, 30);        CacheEntity cacheEntity2=new CacheEntity("2", student2, 30);        CacheEntity cacheEntity3=new CacheEntity("3", student3, 30);        CacheEntity cacheEntity4=new CacheEntity("4", student4, 30);        CacheEntity cacheEntity5=new CacheEntity("5", student5, 30);        CacheEntity cacheEntity6=new CacheEntity("6", student6, 30);        //添加缓存        CacheByHashMap.addCache(cacheEntity1.getKey(), cacheEntity1, cacheEntity1.getTime());        CacheByHashMap.addCache(cacheEntity2.getKey(), cacheEntity2, cacheEntity2.getTime());        CacheByHashMap.addCache(cacheEntity3.getKey(), cacheEntity3, cacheEntity3.getTime());        CacheByHashMap.addCache(cacheEntity4.getKey(), cacheEntity4, cacheEntity4.getTime());        CacheByHashMap.addCache(cacheEntity5.getKey(), cacheEntity5, cacheEntity5.getTime());        CacheByHashMap.addCache(cacheEntity6.getKey(), cacheEntity6, cacheEntity6.getTime());        if(CacheByHashMap.isContainsKey("2"))        {            System.out.println(" 该对象已有缓存");            //这里就可以获取缓存如get()        }        else {            CacheByHashMap.addCache(cacheEntity2.getKey(), cacheEntity2, cacheEntity2.getTime());            //这里可以模拟从数据库获取数据,添加到缓存        }    }}

运行结果:

该对象已有缓存清除过期缓存1清除过期缓存2清除过期缓存3清除过期缓存4清除过期缓存5清除过期缓存6过期队列为空!过期队列为空!
1 0