(十八)java Map接口

1. Map接口

1.1 Map简介

Map 集合是以 Key-Value 键值对作为存储元素实现的哈希结构, Key是不可重复的, Value 则是可以重复的。 Map 类提供三种 Collection 视图,在集合框架图中, Map 指向 Collection 的箭头仅表示两个类之间的依赖关系 。 可以使用keySet()查看所有的 Key,使用 values()查看所有的 Value ,使用entrySet()查看所有的键值对。 HashMap线程是不安全的。 ConcurrentHashMap 是线程安全的。在多线程并发场景中,优先推荐使用ConcurrentHashMap 。 TreeMap 是内容有序。
在这里插入图片描述

1.2 Map结构

  • Map与Collection并列存在,用于保存具有映射关系的数据,Key-value
  • Map中的key和value都可以是任何引用类型得到数据
  • Map中的key用set来存放,不允许重复,所以Map对象所对应的类,必须重写hasCode()和equals()方法
  • key 和 value 之间存在 key 找到value的关系map.get(key)value用Collection存储
  • Map实现类由: HashMap、 TreeMap、 LinkedHashMap 其中HashMap使用频率最高

1.3 Map方法

  • 添加、 删除、修改操作:
    • Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
    • void putAll(Map m):将m中的所有key-value对存放到当前map中
    • Object remove(Object key):移除指定key的key-value对,并返回value
    • void clear():清空当前map中的所有数据
  • 元素查询的操作:
    • Object get(Object key):获取指定key对应的value
    • boolean containsKey(Object key):是否包含指定的key
    • boolean containsValue(Object value):是否包含指定的value
    • int size():返回map中key-value对的个数
    • boolean isEmpty():判断当前map是否为空
    • boolean equals(Object obj):判断当前map和参数对象obj是否相等
  • 元视图操作的方法:
    • Set keySet():返回所有key构成的Set集合
    • Collection values():返回所有value构成的Collection集合
    • Set entrySet():返回所有key-value对构成的Set集合
public class MapDemo {
    public static void main(String[] args) {
        Map map = new HashMap();//存
        map.put("aaaa", 11);
        map.put("aaaa", 1111);//key不可重复,所以直接将上面的覆盖掉
        map.put("cccc", 33);
        map.put("dddd", 44);
        map.put("ffff", 666);
        map.put("4444", 666);
        map.put("2222", 666);
//        map.remove("cccc");//移除某个键值对
        System.out.println(map.get("aaaa"));//取值
        System.out.println(map.containsKey("4444"));//是否包含指定的key
        System.out.println(map.containsValue(666));//是否包含指定的value
        System.out.println(map.size());//键值对的个数
        System.out.println(map.keySet());//返回所有key组成的set集合
        System.out.println(map.values());//返回所有value组成的collection集合
        Set set = map.entrySet();//返回键值对构成的set集合  输出形式为即:key=value
//        循环方式一
        for (Object o : set) {
            System.out.println(o);
        }
运行结果        
2222=666
4444=666
aaaa=1111
cccc=33
dddd=44
ffff=666
        //方式2:使用Entry循环(常用)
        for (Object obj : set) {
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey() + "\t" + entry.getValue());
        }       
        //方式三iterator
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            Map.Entry mp = (Map.Entry) iterator.next();
            System.out.println(mp.getKey() + "\t" + mp.getValue());
        }      
        //方式四 使用map的keyset()方法(常用)
        for (Object obj : map.keySet()) {
            System.out.println(obj + "\t" + map.get(obj));
        }
    }
}
2222	666
4444	666
aaaa	1111
cccc	33
dddd	44
ffff	666

2. HashMap

2.1 简介

● 初始大小为0,添加第一个元素时扩容到16;
● 允许使用null键和null值,与HashSet一样,存储无序,按照hashcode值存储
● 所有的key构成的集合是Set:无序的、不可重复的。所以, key所在的类要重写:equals()和hashCode()
● 所有的value构成的集合是Collection:无序的、可以重复的。所以, value所在的类要重写: equals()
● 一个key-value构成一个entry
● 所有的entry构成的集合是Set:无序的、不可重复的
● HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。
● HashMap 判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true。

2.2 HashMap细节

JDK 7及以前版本

HashMap是数组+链表结构
在这里插入图片描述

  • 当实例化一个HashMap时,系统会创建一个长度为Capacity的Entry数组, 这个长度在哈希表中被称为容量(Capacity), 在这个数组中可以存放元素的位置我们称之为“桶” (bucket), 每个bucket都有自己的索引, 系统可以根据索引快速的查找bucket中的元素。
  • 每个bucket中存储一个元素, 即一个Entry对象, 但每一个Entry对象可以带一个引用变量, 用于指向下一个元素, 因此, 在一个桶中, 就有可能生成一个Entry链。而且新添加的元素作为链表的head。

添加元素的过程
map.put(key1,value1)
首先会调用key1所在类的hashCode()方法计算key1的哈希值,然后通过某种算法计算出key1在Entry数组中的存放位置。
如果此位置上没有存放数据,则(key1-value1)添加成功。
如果此位置上有数据,则比较key1和已经存放的数据的哈希值
  如果key1的哈希值和已经存放的数据的哈希值都不相同,则(key1-value1)添加成功。
  如果key1的哈希值和已经存放的某一个数据(key2-value2)的哈希值相同,则调用key1所在类的equals(key2)方法
    如果equals返回false,则(key1-value1)添加成功。
    如果equals返回true,则使用value1替换value2。
(总的来说每次添加都会成功)

JDK8 存储结构

JDK 8版本发布以后: HashMap是数组+链表+红黑树实现
在这里插入图片描述

当实例化一个HashMap时, 会初始化initialCapacity和loadFactor, 在put第一对映射关系时, 系统会创建一个长度为initialCapacity的Node数组, 
这个长度在哈希表中被称为容量(Capacity), 在这个数组中可以存放元素的位置我们称之为“桶” (bucket), 每个bucket都有自己的索引, 系统可以
根据索引快速的查找bucket中的元素。

添加元素的过程

每个bucket中存储一个元素, 即一个Node对象, 但每一个Node对象可以带一个引用变量next, 用于指向下一个元素, 因此, 在一个桶中,
就有可能生成一个Node链。 也可能是一个一个TreeNode对象, 每一个TreeNode对象可以有两个叶子结点left和right, 因此, 在一个桶中, 
就有可能生成一个TreeNode树。 而新添加的元素作为链表的last, 或树的叶子结点。

HashMap的扩容

  • 当HashMap中的元素越来越多的时候, hash冲突的几率也就越来越高, 因为数组的长度是固定的。 所以为了提高查询的效率, 就要对HashMap的数组进行扩容, 而在HashMap数组扩容之后, 最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置, 并放进去, 这就是resize。
  • 那么HashMap什么时候进行扩容呢? 当HashMap中的元素个数超过数组大小loadFactor 时 , 就 会 进 行 数 组 扩 容 , loadFactor 的 默 认 值为0.75。 也就是说, 默认情况下, 数组大小为16, 那么当HashMap中元素个数超过16 * 0.75=12 的时候, 就把数组的大小扩大一倍, 然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作, 所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

HashMap树化和链化

当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,
那么这个链会变成树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后,下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。

扩容和树化举个例子
○ 初始情况下,hashmap 的capacity为16,因子为0.75。
○ 当hashmap桶内元素小于等于8,且size小于12时,不进行扩容和树化的操作。
○ 当hashmap桶内元素为9,因为capacity为16,因此不进行树化,而选择扩容,将capacity扩容为32。
○ 当hashmap桶内元素为10,因为capacity为32,因此不进行树化,而选择扩容,将capacity扩容为64。
○ 当hashmap桶内元素大于10,由于capacity已经达到64,此时进行树化(用红黑树存储)。
○ 最后当HashMap中元素个数超过48(64*0.75=48),进行扩容

JDK1.8HashMap新变化

  • HashMap map = new HashMap();//默认情况下大小为0
  • 当首次调用map.put()时,再创建长度为16的数组
  • 数组为Node类型,在jdk7中称为Entry类型
  • 形成链表结构时,新添加的key-value对在链表的尾部
    ● table: 存储元素的数组,总是2的n次幂
    ● entrySet: 存储具体元素的集合
    ● size: HashMap中存储的键值对的数量
    ● modCount: HashMap扩容和结构改变的次数。
    ● threshold: 扩容的临界值, =容量*填充因子
    ● loadFactor: 填充因子
    ● resize:扩容
    ● treeifyBin:树化
    ● untreeify:链化

2.3 哈希取模算法

● 调用对象的hashCode(),确定数组中的槽位,确定数组中的槽位,采用的哈希取模算法
● put(key) —> (n-1) & hash
● 要求容量必须是2的n次方

2.4 面试题

HashMap的容量,为什么必须是2的n次幂
hashMap存储元素时要使用hashCode值来确定放在哪一个桶上,而计算方法为(n-1 & hash)即:hash%n;使用与计算速度要快,但前提是n必须是2的n次幂,否则 HashMap会自动转换为大于这个数的2的n次幂。
请添加图片描述

3.LinkedHashMap

● LinkedHashSet和LinedHashMap的关系,从逻辑上这两个集合实现方式完全一致,只是LinkedHashSet使用LinkedHashMap实现,只有key,而value是一个静态的空对象
● 底层使用链表实现
● 有顺序(插入顺序),没有重复的集合(看上去有序)

4. TreeMap

● TreeSet使用TreeMap实现。
● TreeMap存储 Key-Value 对时, 需要根据 key-value 对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态。
● TreeSet底层使用红黑树结构存储数据
● TreeMap 的 Key 的排序:
请添加图片描述

4.1 自然排序

自然排序: TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException

public class TreeMapDemo {
    public static void main(String[] args) {
        User u1 = new User("ggg", 35);
        User u2 = new User("ggg", 35);
        User u3 = new User("ccc", 83);
        User u4 = new User("ccc", 30);
        User u5 = new User("ccc", 74);
        User u6 = new User("eee", 39);
        User u7 = new User("fff", 40);
        User u8 = new User("aaa", 40);
        User u9 = new User("bbb", 40);
        TreeMap map = new TreeMap();
        map.put(u1, "a");
        map.put(u2, "a");
        map.put(u3, "a");
        map.put(u4, "a");
        map.put(u5, "a");
        map.put(u6, "a");
        map.put(u7, "a");
        map.put(u8, "a");
        map.put(u9, "a");
        for (Object o : map.entrySet()) {
            System.out.println(o);
        }
    }
}
public class User implements Comparable {

    private String name;
    private int age;
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    //按照姓名从大到小排列,年龄从小到大排列
    @Override
    public int compareTo(Object o) {
        if(o instanceof User){
            User user = (User)o;
//            return -this.name.compareTo(user.name);
//            int compare = -this.name.compareTo(user.name);
            int compare = this.name.compareTo(user.name);
            if(compare != 0){
                return compare;
            }else{
                return Integer.compare(this.age,user.age);
            }
        }else{
            throw new RuntimeException("输入的类型不匹配");
        }
    }
}
User{name='aaa', age=40}=a
User{name='bbb', age=40}=a
User{name='ccc', age=30}=a
User{name='ccc', age=74}=a
User{name='ccc', age=83}=a
User{name='eee', age=39}=a
User{name='fff', age=40}=a
User{name='ggg', age=35}=a

4.2 定制排序

定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现Comparable 接口

  • TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0,1,-1
    • 0:对象相等,添加失败
    • -1:比对象小,添加到左边
    • 1:比对象打,添加到右边
public class TreeMapDemo {
    public static void main(String[] args) {
        User u1 = new User("ggg", 35);
        User u2 = new User("ggg", 35);
        User u3 = new User("ccc", 83);
        User u4 = new User("ccc", 30);
        User u5 = new User("ccc", 74);
        User u6 = new User("eee", 39);
        User u7 = new User("fff", 40);
        User u8 = new User("aaa", 40);
        User u9 = new User("bbb", 40);
        TreeMap map = new TreeMap(new Comparator() {//匿名内部类
            @Override
            public int compare(Object o1, Object o2) {
                if ((o1 instanceof User) && (o2 instanceof User)) {
                    User user1 = (User) o1;
                    User user2 = (User) o2;
                    int compare = user1.name.compareTo(user2.name);
                    if (compare != 0) {
                        return compare;
                    } else {
                        return Integer.compare(user2.age, user1.age);
                    }
                } else {
                    throw new RuntimeException("输入的类型不匹配");
                }
            }
        });
        map.put(u1, "a");
        map.put(u2, "a");
        map.put(u3, "a");
        map.put(u4, "a");
        map.put(u5, "a");
        map.put(u6, "a");
        map.put(u7, "a");
        map.put(u8, "a");
        map.put(u9, "a");
        for (Object o : map.entrySet()) {
            System.out.println(o);
        }
    }
}
User{name='aaa', age=40}=a
User{name='bbb', age=40}=a
User{name='ccc', age=83}=a
User{name='ccc', age=74}=a
User{name='ccc', age=30}=a
User{name='eee', age=39}=a
User{name='fff', age=40}=a
User{name='ggg', age=35}=a
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值