Map集合

作者: 江湖非良人 | 来源:发表于2019-08-06 12:42 被阅读17次

      之前对Collection接口以及其对应的子接口已经有所了解,可以发现在Collection接口之中所保存的数据全部都只是单个对象,而在数据结构中除了可以进行单个对象的保存外,也可以进行二元偶对象的保存(key=value)的形式来存储,而存储二元偶对象的核心意义在于需要通过key获取对应的value。

    在开发中,Collection集合保存数据的目的是为了输出,Map集合保存数据的目的是为了进行key的查找。

    Map接口简介

      Map接口是进行二元偶对象保存的最大父接口。该接口定义如下:

    public interface Map<K,V>
    

      该接口为一个独立的父接口,并且在进行接口对象实例化的时候需要设置K与V的类型,也就是在整体操作的时候需要保存两个内容,在Map接口中定义有许多操作方法,但是需要记住以下的核心操作方法:

    • 向集合中保存数据V put​(K key,V value)
    • 根据key查询数据V get​(Object key)
    • 将Map集合转为Set集合Set<Map.Entry<K,V>> entrySet()
    • 指定key是否存在:boolean containsKey​(Object key)
    • 将Map集合中的key转为Set集合:Set<K> keySet()
    • 根据key删除指定的数据:V remove​(Object key)

      从JDK1.9后Map接口中也扩充了一些静态方法提供用户使用。
    范例:观察Map集合的特点

    import java.util.Map;
    public class JavaAPIDemo {
        public static void main(String[] args) throws Exception {
    //        Map<String,Integer> map=Map.of("one",1,"two",2);
    //        System.out.println(map);//{two=2, one=1}
    //       
    //        Map<String,Integer> map=Map.of("one",1,"two",2,"one",101);
    //        System.out.println(map);//Exception in thread "main" java.lang.IllegalArgumentException: duplicate key: one
            
            Map<String,Integer> map=Map.of("one",1,"two",2,null,0);
            System.out.println(map);//Exception in thread "main" java.lang.NullPointerException
        }
    }
    

    在Map集合中数据的保存就是按照“key=value”的形式存储的,并且使用of()方法时里面的key是不允许重复,如果重复则会出现java.lang.IllegalArgumentException: duplicate key: one,如果设置的key或value为null,则会出现java.lang.NullPointerException

      对于现在使用的of()方法严格意义上来说并不是Map集合的标准用法,因为正常的开发中需要通过Map集合的子类来进行接口对象的实例化,而常用的子类:HashMap、HashTable、TreeMap、LinkedHashMap。

    image.png

    HashMap子类

      HashMap是Map接口中最为常见的一个子类,该类的主要特点是无序存储,通过Java文档首先来观察一下HashMap子类的定义:

    public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
    
    public abstract class AbstractMap<K,V> extends Object implements Map<K,V>
    

    该类的定义继承形式符合之前的集合定义形式,依然提供有抽象类并且依然需要重复实现Map接口。

    范例:观察Map集合的使用

    import java.util.HashMap;
    import java.util.Map;
    public class JavaAPIDemo {
        public static void main(String[] args) throws Exception {
            Map<String,Integer> map=new HashMap();
            map.put("1",1);
            map.put("2",2);
            map.put("1",101);//key重复
            map.put(null,0);//key为null
            map.put("0",null);//value为null
            System.out.println(map.get("1"));//key存在:101
            System.out.println(map.get(null));//key存在:0
            System.out.println(map.get("3"));//key不存在:null
        }
    }
    

    以上的操作形式为Map集合使用的最标准的处理形式,通过代码可以发现,通过HashMap实例化的Map接口可以针对key或者value保存null的数据,同时也可以发现即便保存数据的key重复,那么也不会出现错误,而是内容的覆盖。

      对于Map接口中提供的put()方法本身是提供有返回值的,那么这个返回值指的是在重复key的情况下返回旧的value。
    范例:观察put()方法

    import java.util.HashMap;
    import java.util.Map;
    public class JavaAPIDemo {
        public static void main(String[] args) throws Exception {
            Map<String, Integer> map = new HashMap();
            Integer oldValue = map.put("1", 1);
            System.out.println(oldValue);//key不重复,返回null:null
            oldValue = map.put("1", 101);
            System.out.println(oldValue);//key重复,返回旧数据:1
        }
    }
    

    在设置了相同key的内容时,put()方法会返回原始的数据内容。

      清楚了HashMap的基本功能后下面就需要来研究一下HashMap中给出的源代码。

    final float loadFactor;
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    

    当使用无参构造的时候,会出现有一个loadFactor属性,并且该属性默认的内容为“0.75”

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    

    在使用put()方法进行数据保存时会调用一个putVal()方法,同时会将key进行hash处理(生成一个hash编码),而对于putVal()方法中会发现会提供一个Node节点类进行数据的保存,而在使用putVal()方法操作的过程中,会调用一个resize()方法可以进行容量的扩充。

    LinkedHashMap

      HashMap虽然是Map集合中最为常用的子类,但是其本身保存的数据都是无序的(有序与否对Map没有影响),如果现在希望Map集合中的保存的数据的顺序为其增加的顺序,则就可以更换子类为LinkedHashMap(基于链表实现的),观察LinkedHashMap的定义:

    public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
    

      既然是链表保存,所以一般在使用LinkedHashMap类时数据量不要特别大,因为会造成时间复杂度攀升,通过继承的结构可以发现LinkedHashMap是HashMap的子类,继承关系如下:

    LinkedHashMap

    范例:使用LinkedHashMap

    import java.util.LinkedHashMap;
    import java.util.Map;
    public class JavaAPIDemo {
        public static void main(String[] args) throws Exception {
            Map<String, Integer> map = new LinkedHashMap<String, Integer>();
            map.put("1", 1);
            map.put("2", 2);
            map.put("5", 5);
            map.put("4", 4);
            map.put("3", 3);
            System.out.println(map);//{1=1, 2=2, 5=5, 4=4, 3=3}
        }
    }
    

    通过此时的程序执行可以发现当使用LinkedHashMap进行存储之后所有数据的保存顺序为添加顺序。

    HashTable

      HashTable类是从JDK1.0时提供的,与Vector、Enumeration属于最早的一批动态数组的实现类,后来为了将其继续保留下来,所以让其多实现了一个Map接口,HashTable的定义如下:

    public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, Serializable
    
    public abstract class Dictionary<K,V> extends Object
    

      HashTable的继承结构如下:

    HashTable

    范例:观察HashTable类的使用

    import java.util.Hashtable;
    import java.util.Map;
    public class JavaAPIDemo {
        public static void main(String[] args) throws Exception {
            Map<String, Integer> map = new Hashtable();
            map.put("1", 1);
            map.put("2", 2);
            map.put("5", 5);
            map.put(null, 4);//不能为空
            map.put("3",null);//不能为空
            System.out.println(map);
            //Exception in thread "main" java.lang.NullPointerException
        }
    }
    

    通过观察可以发现在HashTable中进行数据存储时设置的key和value都不允许为null,否则会出现NullPointerException异常。

    Map.Entry接口

      虽然清楚了整个Map集合的基本操作形式,但是依然有一个核心问题要解决,Map集合中是如何进行数据存储的?对于List而言(LinkedList)依靠的是链表的形式实现的数据存储,那么在进行数据存储的时一定要将数据保存在Node节点中,虽然HashMap中也可以见到Node类型定义,通过源代码定义可以发现,HashMap类中Node内部类本身实现Map.Entry接口。

    static class Node<K,V> implements Map.Entry<K,V> 
    

      所以可以得出结论:所有的key和value的数据都被封装在Map.Entry接口之中,而此接口定义如下:

    public static interface Map.Entry<K,V>
    

      在这个内部接口中提供了两个重要的操作方法:

    • 获取key:K getKey()
    • 获取value:V getValue()

      在JDK1.9以前的开发版本中,开发者基本上都不会去考虑创建Map.Entry的对象,实际上在正常的开发过程中开发者也不需要关心Map.Entry对象,但是从JDK1.9后,Map接口追加有一个新的方法:

    • 创建Map.Entry对象:static <K,V> Map.Entry<K,V> entry​(K k, V v)
      范例:创建Map.Entry对象
    import java.util.Map;
    public class JavaAPIDemo {
        public static void main(String[] args) throws Exception {
            Map.Entry<String, Integer> entry = Map.entry("one",1);
            System.out.println(entry.getKey());//one
            System.out.println(entry.getValue());//1
            System.out.println(entry.getClass().getName());//java.util.KeyValueHolder
        }
    }
    

    通过分析可以发现在整个Map集合中,Map.Entry的主要作用就是作为一个Key和Value的包装类型使用,而大部分情况下在进行数据存储的时候都会将Key和Value包装为一个Map.Entry对象进行使用。

    使用Iterator输出Map集合

      对于集合的输出而言,最标准的做法就是利用Iterator接口来完成,但是需要明确一点就是在Map集合中并没有方法可以直接返回Iterator接口对象,所以这种情况下就必须分析不直接提供Iterator接口实例化的方法的原因,下面对Collection和Map集合的存储结构进行一个比较。

    Collection与Map

      发现在Map集合中保存的实际上是一组Map.Entry接口对象(里面包装的是Key与Value),所以整个来说Map依然实现的是单值的保存,查看文档可以发现Map集合中提供了一个方法“Set<Map.Entry<K,V>> entrySet()”,将全部的Map集合转为Set集合。

      经过分析可以发现,如果想要使用Iterator实现Map集合的输出则必须按照如下步骤处理:

    • 利用Map接口中提供的entrySet()方法见Map集合转为Set集合;
    • 利用Set接口中的iterator()方法将Set集合转为Iterator接口实例;
    • 利用Iterator进行迭代输出获取每一组的Map.Entry对象,随后通过getKey()和getValue()获取数据。

    范例:利用Iterator输出Map集合

    import java.util.Iterator;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    public class JavaAPIDemo {
        public static void main(String[] args) throws Exception {
            Map<String, Integer> map = new HashMap();
            map.put("one", 1);
            map.put("two", 2);
            Set<Map.Entry<String, Integer>> set = map.entrySet();
            Iterator<Map.Entry<String, Integer>> iterator = set.iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, Integer> entry = iterator.next();
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
        }
    }
    

    虽然Map集合可以支持迭代输出,但是从实际开发来说,Map集合最主要的用法在于实现数据的Key查找操作,另外需要注意的是,如果现在不适用Iterator而使用foreach语法输出则也需要将Map集合转为Set集合。

    范例:利用foreach输出Map集合

    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    public class JavaAPIDemo {
        public static void main(String[] args) throws Exception {
            Map<String, Integer> map = new HashMap();
            map.put("one", 1);
            map.put("two", 2);
            Set<Map.Entry<String, Integer>> set = map.entrySet();
            for (Map.Entry<String, Integer> entry:set) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
        }
    }
    

    由于Map迭代输出的情况相对较少,所以对于此类的语法应该深入理解一下,并且一定要灵活掌握。

    关于KEY的定义

      在使用Map集合时可以发现对于Key和Value的类型实际上都可以由开发者任意决定,那么意味着现在依然可以使用自定义类来进行Key类型的设置。对于自定义Key类型所在类中一定要覆写hashCode()和equals()方法,否则无法查找到。

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    

    在进行数据保存的时候发现会自动使用传入的key的数据数据生成一个hash码,也就是说存储的时候是有这个Hash数值。
    在根据key获取数据的时候依然要将传入的key通过hash()方法来获取其对应的hash码,也就是说查询的过程中首先要利用hashCode()来进行数据查询,当使用getNode()方法查询时还需要使用到equals()方法。

    范例:使用自定义类作为Key类型

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import java.util.HashMap;
    import java.util.Map;
    @Data
    @AllArgsConstructor
    class Person{
        private String name;
        private int age;
    }
    public class JavaAPIDemo {
        public static void main(String[] args) throws Exception {
            Map<Person, String> map = new HashMap();
            map.put(new Person("张三",20), "HELLO");
            map.put(new Person("李四",31), "WORLD");
            System.out.println(map.get(new Person("张三",20)));//null
        }
    }
    

    虽然运行你使用自定义的类作为Key的类型,但是而需要注意一点,在实际的开发之中对于Map集合的Key常用的三种类型:String、Long、Integer。

    相关文章

      网友评论

        本文标题:Map集合

        本文链接:https://www.haomeiwen.com/subject/jlfidctx.html