Java集合Map家族成员(HashMap、LinkedHashMap、TreeMap、HashTable、ConcurrentHashMap)

ArrayMap、SparseArray

在这里插入图片描述

HashMap原理

  • HashMap 基于 Hash 算法实现的,用于存储key、value键值对。当传入 key 时,获取key的hash 值,根据 hash 值计算出散列地址,将 value 保存在该地址里。当计算出的散列地址相同时,我们称之为 hash冲突,HashMap的做法是用链表和红黑树存储相同散列地址的 value。当 hash 冲突的个数比较少时,使用链表否则使用红黑树。
  • 取值的时候如果对比节点的哈希值和要查找的哈希值相等,就会判断 key 是否相等(链表和红黑树情况下),相等就直接返回;不相等就从子树中递归查找。
  • 当一个链表上的元素个数大于8时,会将链表转为红黑树,主要是为了在数据量大的情况下,加快数据的查找效率。毕竟一个红黑树的插入和查找的时间复杂度为O(logn),远比链表的插入和查询的时间复杂度O(n)要好的多,而且数据量越大,越明显。
  • 平衡二叉树的插入和查找时间复杂度为O(logn)
  • 参考资料

HashMap的构造方法

  • public HashMap(int initialCapacity, float loadFactor)
    默认初始容量是16,initialCapacity
    默认的负载因子是0.75,loadFactor
    • 关于initialCapacity:
      例如new HashMap(3)会创建一个大于3的2的n次方的长度的HashMap,为4
      例如new HashMap(5)会创建一个大于5的2的n次方的长度的HashMap,为8
  • 扩容:
    当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),哈希表扩容一倍。rehash消耗性能,所以可以通过设置合理的初始容量来减少rehash次数。
  • 问题:new HashMap(65,0.75f)要录入95条数据,会产生扩容吗?
    解答:创建的HashMap的长度为,2的6次方=64,不够申请的65,所以需要申请2的7次方=128,触发扩容的大小为128*0.75=96条数据,所以不会进行扩容。

LinkedHashMap

  • 继承自HashMap,里面构造类似于双向链表原理。Android中的缓存算法LRUCache就是基于LinkedHashMap实现的。

  • 特点:
    1.是有序的(录入顺序,使用顺序)。而HashMap是无序的。
    2.一次性取大量数据的情况下会快(例如遍历)。

  • 对于录入顺序(最新添加的排在后面)

    public static Map<String, Integer> getMap() {
        Map<String, Integer> map = new LinkedHashMap<>();
        map.put("zhangyu1", 1);
        map.put("zhangyu2", 2);
        map.put("zhangyu3", 3);
        map.put("zhangyu4", 4);
        map.put("zhangyu5", 5);
        return map;
    }

//对于录入顺序
{zhangyu1=1, zhangyu2=2, zhangyu3=3, zhangyu4=4, zhangyu5=5}

  • 对于使用顺序(最近添加或访问的排在后面)
    第三个参数,accessOrder = true
    public static Map<String, Integer> getMap() {
    	//对于使用顺序,要修改构造方法,true
        Map<String, Integer> map = new LinkedHashMap<>(16, 0.75f, true);
        map.put("zhangyu1", 1);
        map.put("zhangyu2", 2);
        map.put("zhangyu3", 3);
        map.put("zhangyu4", 4);
        map.put("zhangyu5", 5);

        map.get("zhangyu1");//LinkedHashMap使用后,排在最后

        return map;
    }
    
//使用顺序,输出结果
{zhangyu2=2, zhangyu3=3, zhangyu4=4, zhangyu5=5, zhangyu1=1}

  • LRU是Least Recently Used的缩写,即最近最少使用
import java.util.LinkedHashMap;
import java.util.Map;

public class LRUMap<K, V> extends LinkedHashMap<K, V> {

    private int maxSize;

    public LRUMap(int maxSize) {
        super(16, 0.75f, true);
        this.maxSize = maxSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > this.maxSize;
    }
}

public class LRUMapTest {
    public static void main(String[] args) {
        Map<String,Integer> lruMap = new LRUMap(3);
        lruMap.put("zhangyu1",1);
        lruMap.put("zhangyu2",2);
        lruMap.put("zhangyu3",3);
        lruMap.put("zhangyu4",4);
        lruMap.put("zhangyu5",5);
        System.out.println(lruMap);
    }
}

//结果:{zhangyu3=3, zhangyu4=4, zhangyu5=5}



public class LRUMapTest {
    public static void main(String[] args) {
        Map<String,Integer> lruMap = new LRUMap(3);
        lruMap.put("zhangyu1",1);
        lruMap.put("zhangyu2",2);
        lruMap.put("zhangyu3",3);
        System.out.println(lruMap);

        lruMap.get("zhangyu1");
        System.out.println(lruMap);

        lruMap.put("zhangyu4",4);
        lruMap.put("zhangyu5",5);
        System.out.println(lruMap);
    }
}

//结果
//{zhangyu1=1, zhangyu2=2, zhangyu3=3}
//{zhangyu2=2, zhangyu3=3, zhangyu1=1}
//{zhangyu1=1, zhangyu4=4, zhangyu5=5}

TreeMap

  • TreeMap按照key的值排序,因为有排序所以速度会慢
public class TreeMapTest {
    public static void main(String[] args) {
        Map<String,Integer> map = new TreeMap<>();
        map.put("aaa",1);
        map.put("lkj",4);
        map.put("abc",2);
        map.put("bcc",3);
        map.put("sdf",4);

        System.out.println(map);
    }
}
//{aaa=1, abc=2, bcc=3, lkj=4, sdf=4}




public class TreeMapTest {
    public static void main(String[] args) {

        Map<String,Integer> map = new TreeMap<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o2.compareTo(o1);
            }
        });
        map.put("aaa",1);
        map.put("lkj",4);
        map.put("abc",2);
        map.put("bcc",3);
        map.put("sdf",4);

        System.out.println(map);
    }
}
//{sdf=4, lkj=4, bcc=3, abc=2, aaa=1}

HashTable

  • public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable
  • 线程安全
  • 完全依靠synchronized导致性能低(全部加锁)
    大锁:对HashTable对象加锁
    长锁:直接对方法加锁
    读写锁公用:只有一把锁,从头锁到尾

ConcurrentHashMap

  • public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable
  • 线程安全
  • 减少synchronized的使用,多用volatile
  • 发展历程
    JDK5:分段锁,必要时加锁(缺陷segment分布不均匀)
    JDK6:优化二次Hash算法(使其均匀分布,高位找segment低位找table)
    JDK7:段懒加载,volatile&cas
    JDK8:摒弃段,基于HashMap原理的并发实现(尽量使用volatile和小范围加锁),引入CounterCell,本质上也是分段计数。
  • 弱一致性
    添加元素后不一定能马上读到
    清空元素后可能仍然会有元素
    遍历之前段元素的变化会读到
    遍历之后段元素变化读不到
    遍历时元素发生变化不抛出异常
    ConcurrentHashMap的弱一致性主要是为了提升效率,是一致性与效率之间的一种权衡。要成为强一致性,就得到处使用锁,甚至是全局锁,这就与Hashtable一样了。

Map的遍历

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class MapTest {
    public static void main(String[] args) {
        Map<String, Integer> map = getMap();
        System.out.println(map);
        showMap1(map);
        showMap2(map);
        showMap3(map);
        showMap4(map);
    }

    /**
     * 利用迭代器遍历。
     * 同时获取到key,value
     * 速度方面,values,entrySet,Iterator速度相近
     * @param map
     */
    public static void showMap4(Map<String, Integer> map) {
        System.out.println("--------------------------");
        Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Integer> entry = iterator.next();
            System.out.println(entry.getKey()+"="+entry.getValue());
        }
    }

    /**
     * 利用entrySet进行遍历
     * 同时获取到key,value
     * 速度方面,values,entrySet,Iterator速度相近
     * @param map
     */
    public static void showMap3(Map<String, Integer> map) {
        System.out.println("--------------------------");
        for (Map.Entry<String,Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey()+"="+entry.getValue());
        }
    }

    /**
     * 利用values进行遍历
     * 缺点:只能获取到value
     * 速度方面,values,entrySet,Iterator速度相近
     * @param map
     */
    public static void showMap2(Map<String, Integer> map) {
        System.out.println("--------------------------");
        for (Integer v : map.values()) {
            System.out.println(v);
        }
    }

    /**
     * 利用keySet进行遍历
     * 优势:既可以得到key,又可以得到value
     * 缺点:速度最慢,通常不用
     * @param map
     */
    public static void showMap1(Map<String, Integer> map) {
        System.out.println("--------------------------");
        for (String key : map.keySet()) {
            System.out.println(key+"="+map.get(key));
        }
    }


    /**
     * 初始化一个map
     *
     * @return
     */
    public static Map<String, Integer> getMap() {
        Map<String, Integer> map = new HashMap<>();
        map.put("zhangyu1", 1);
        map.put("zhangyu2", 2);
        map.put("zhangyu3", 3);
        map.put("zhangyu4", 4);
        map.put("zhangyu5", 5);
        return map;
    }
}


输出结果
{zhangyu4=4, zhangyu5=5, zhangyu1=1, zhangyu2=2, zhangyu3=3}
--------------------------
zhangyu4=4
zhangyu5=5
zhangyu1=1
zhangyu2=2
zhangyu3=3

如何避免put会修改之前的值

  • 由于Map中key值是唯一的,所以put一个相同的key,value会被修改。
    有两种方法:
    • 1.if (map.containKey(key))
    • 2.使用putIfAbsent(key,value)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值