• Java集合05:Map接口


    一、Map接口及其多个实现类的对比

    Map:双列数据,存储key-value对的数据,类似于高中的函数:y = f(x)

    • HashMap:作为Map的主要实现类;线程不安全的,效率高;可以存储null的key和value。
      LinkedHashMap:HashMap的子类;保证在遍历Map元素时,可以按照添加顺序实现遍历;(原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。)对于频繁的遍历操作,此类执行效率高于HashMap。
    • TreeMap:保证按照添加的key-value对进行排序,实现排序遍历;(此时考虑key的自然排序或者定制排序)底层使用红黑树。
    • Hashtable:作为Map的古老实现类;线程安全的,效率低;不能存储null的key和value。
      Properties:Hashtable的子类;常用来处理配置文件。key和value都是String类型

    HashMap的底层:数组+链表(jdk7及之前),数组+链表+红黑树(jdk8)

    面试题

    1.HashMap的底层实现原理?
    2.HashMap和Hashtable的异同?

    二、Map中存储的key-value的特点

    Map中的key:无序的、不可重复的,使用Set存储所有的key。
              –> 以HashMap为例:key所在的类要重写equals()和hashCode()
    Map中的value:无序的、可重复的,使用Collection存储所有的value。
              –> 以HashMap为例:value所在的类要重写equals()。
    一个键值对:key-value构成了一个Entry对象。
    Map中的entry:无序的、不可重复的,使用Set存储所有的entry。

    三、HashMap的底层实现原理

    以 jdk7 为例

    HashMap map = new HashMap;
    在实例化以后,底层创建了长度为16的一维数组Entry[] table。
    ...可能已经执行过多次put...
    map.put(key1, value1);
    首先,调用key1所在类的hashCode()计算key1的哈希值,此哈希值经过某种算法计算之后,得到在Entry数组中的存放位置。
    如果此位置上的数据为空,此时的key1-value1添加成功。  ----情况1
    如果此位置上的数据不为空(即此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
            如果key1的哈希值与已经存在数据的哈希值都不相同,此时key1-value1添加成功。  ----情况2
            如果key1的哈希值与已经存在的某一个数据(key2-value2)的哈希值相同,调用key1所在类的equals(key2)方法进行比较:
                    如果equals()返回false:此时key1-value1添加成功。  ----情况3
                    如果equals()返回true:使用value1替换value2。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
    在不断的添加过程中,会涉及到扩容问题。
    当超出临界值(且要存放的位置非空)时,扩容。
    默认的扩容方式:扩容为原来容量的2倍,并将原来的数据复制过来。

    jdk8 相较于 jdk7 在底层实现方面的不同

    1. new HashMap() 底层没有创建一个长度为16的数组。
    2. jdk8 底层的数组是:Node[],而非Entry[]。
    3. 首次调用put()方法时,底层创建长度为16的数组。
    4. jdk7 底层结构只有:数组+链表,jdk8 中底层结构:数组+链表+红黑树
      4.1 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此索引位置上的所有数组改为使用红黑树存储。
      4.2 形成链表时,七上八下:jdk7 --> 新的元素指向旧的元素,jdk8 --> 旧的元素指向新的元素。

    HashMap源码中的重要常量

    DEFAULT_INITIAL_CAPACITY:HashMap的默认容量,16
    MAXIMUM_CAPACITY:HashMap的最大支持容量,2^30
    DEFAULT_LOAD_FACTOR:HashMap的默认加载因子,0.75
    TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树,8
    UNTREEIFY_THRESHOLD:Bucket中红黑树存储的Node小于该默认值,转化为链表
    MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量,64(当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。)
    table:存储元素的数组,总是2的n次幂
    entrySet:存储具体元素的集
    size:HashMap中存储的键值对的数量
    modCount:HashMap扩容和结构改变的次数。
    threshold:扩容的临界值,等于容量*填充因子,16*0.75 => 12
    loadFactor:填充因子

    四、LinkedHashMap的底层实现原理(了解)

    源码

    	static class Entry extends HashMap.Node {
    		Entry before, after; //能够记录添加的元素的先后顺序
    		Entry(int hash, K key, V value, Node next) {
    			super(hash, key, value, next);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    五、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 MapMethod {
        /*
        添加、删除、修改操作
        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中的所有数据
         */
        @Test
        public void test1() {
            HashMap map = new HashMap();
            //添加
            map.put("AA", 123);
            map.put(45, 123);
            map.put("BB", 56);
            //修改
            map.put("AA", 87);
    
            System.out.println(map); //{AA=87, BB=56, 45=123}
    
            HashMap map1 = new HashMap();
            map1.put("CC", 123);
            map1.put("DD", 123);
    
            map.putAll(map1);
    
            System.out.println(map); //{AA=87, BB=56, CC=123, DD=123, 45=123}
    
            //remove(Object key)
            Object value = map.remove("CC");
            System.out.println(value); //123
            System.out.println(map); //{AA=87, BB=56, DD=123, 45=123}
    
            //clear()
            map.clear();
            System.out.println(map.size()); //0
            System.out.println(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是否相等
         */
        @Test
        public void test2() {
            HashMap map = new HashMap();
            map.put("AA", 123);
            map.put(45, 123);
            map.put("BB", 56);
    
            //Object get(Object key)
            System.out.println(map.get(45)); //123
    
            //boolean containsKey(Object key)
            boolean isExist = map.containsKey("BB");
            System.out.println(isExist); //true
    
            //boolean containsValue(Object value)
            isExist = map.containsValue(123);
            System.out.println(isExist); //true
    
            //int size()
            System.out.println(map.size()); //3
    
            //boolean isEmpty()
            map.clear();
            System.out.println(map.isEmpty()); //true
        }
        /*
        元视图操作的方法
        Set keySet():返回所有key构成的Set 集合
        Collection values():返回所有value构成的Collection集合
        Set entrySet():返回所有key-value对构成的Set集合
         */
        @Test
        public void test3() {
            HashMap map = new HashMap();
            map.put("AA", 123);
            map.put(45, 1234);
            map.put("BB", 56);
    
            //遍历所有的key集:keySet()
            Set set = map.keySet();
            Iterator iterator = set.iterator();
            while (iterator.hasNext()) {
                System.out.println(iterator.next());
            }
            System.out.println();
    
            //遍历所有的value集:values()
            Collection values = map.values();
            for(Object obj : values) {
                System.out.println(obj);
            }
            System.out.println();
    
            //遍历所有的key-value:entrySet()
            //方式一:
            Set entrySet = map.entrySet();
            Iterator iterator1 = entrySet.iterator();
            while (iterator1.hasNext()) {
                Object obj = iterator1.next();
                //entrySet集合中的元素都是entry
                Map.Entry entry = (Map.Entry) obj;
                System.out.println(entry.getKey() + "---->" + entry.getValue());
            }
            System.out.println();
            //方式二:
            Set keySet = map.keySet();
            Iterator iterator2 = keySet.iterator();
            while (iterator2.hasNext()) {
                Object key = iterator2.next();
                System.out.println(key + "====" + map.get(key));
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122

    总结

    添加:put(Object key, Object value)
    删除:remove(Object remove)
    修改:put(Object key, Object value)
    查询:get(Object key)
    长度:size()
    遍历:keySet() / values() / entrySet()

    六、TreeMap两种添加方式的使用

    向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
    因为要按照key进行排序:自然排序、定制排序

    
    public class TreeMapTest {
    
        //自然排序
        @Test
        public void test1() {
            TreeMap map = new TreeMap();
            User u1 = new User("Tom", 23);
            User u2 = new User("Jerry", 32);
            User u3 = new User("Jack", 20);
            User u4 = new User("Rose", 18);
    
            map.put(u1, 98);
            map.put(u2, 89);
            map.put(u3, 76);
            map.put(u4, 100);
    
            Set set = map.entrySet();
            Iterator iterator = set.iterator();
            while (iterator.hasNext()) {
                Object obj = iterator.next();
                Map.Entry entry = (Map.Entry) obj;
                System.out.println( entry.getKey() + "--->" + entry.getValue());
            }
        }
    
        //定制排序
        @Test
        public void test2() {
            TreeMap map = new TreeMap(new Comparator() {
                @Override
                public int compare(Object o1, Object o2) {
                    if (o1 instanceof User && o2 instanceof User) {
                        User u1 = (User) o1;
                        User u2 = (User) o2;
                        return Integer.compare(u1.getAge(), u2.getAge());
                    }
                    throw new RuntimeException("输入的类型不匹配");
                }
            });
            User u1 = new User("Tom", 23);
            User u2 = new User("Jerry", 32);
            User u3 = new User("Jack", 20);
            User u4 = new User("Rose", 18);
    
            map.put(u1, 98);
            map.put(u2, 89);
            map.put(u3, 76);
            map.put(u4, 100);
    
            Set set = map.entrySet();
            Iterator iterator = set.iterator();
            while (iterator.hasNext()) {
                Object obj = iterator.next();
                Map.Entry entry = (Map.Entry) obj;
                System.out.println( entry.getKey() + "--->" + entry.getValue());
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    
    public class User implements Comparable {
    
        private String name;
        private int age;
    
        public User() {
        }
    
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
    
            User user = (User) o;
    
            if (age != user.age) return false;
            return Objects.equals(name, user.name);
        }
    
        @Override
        public int hashCode() {
            int result = name != null ? name.hashCode() : 0;
            result = 31 * result + age;
            return result;
        }
    
        //按照姓名从大到小排列,年龄从小到大排列
        @Override
        public int compareTo(Object o) {
            if (o instanceof User) {
                User User = (User) o;
    //            return -this.name.compareTo(person.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("输入的类型不匹配");
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    七、Properties处理属性文件

    properties类是Hashtable的子类,该对象用于处理属性文件。
    由于属性文件里的key、value都是字符串类型,所以Poperties里的key和value都是字符串类型
    存取数据时,建议使用setProperty(String key, String value)方法和getProperty(String key)方法

    
    public class PropertiesTest {
    
        //Properties:常用来处理配置文件,key和value都是String类型
        public static void main(String[] args) {
            FileInputStream file = null;
            try {
                Properties pros = new Properties();
    
                file = new FileInputStream("jdbc.properties");
                pros.load(file); //加载流对应的文件
    
                String name = pros.getProperty("name");
                String password = pros.getProperty("password");
    
                System.out.println("name = " + name + ", password = " + password);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (file != null) {
                    try {
                        file.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    八、Collections工具类常用方法

    Collections是一个操作Set、List和Map等集合的工具类。
    Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作。还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。

    排序操作:(均为静态方法)

    reverse(List):反转List中元素的顺序
    shuffle(List):对List集合元素进行随机排序
    sort(List):根据元素的自然顺序对指定List集合元素按升序排序
    sort(List, Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
    swap(List, int, int):将指定List集合中的 i 处元素和 j 处元素进行交换

    查找、替换:

    Object max(Collection):根据元素的自然顺序,返回指定集合中的最大元素
    Object max(Collection, Comparator):根据Comparator指定的顺序,返回指定集合中的最大元素
    Object min(Collection):根据元素的自然顺序,返回指定集合中的最小元素
    Object min(Collection, Comparator):根据Comparator指定的顺序,返回指定集合中的最小元素
    int frequency(Collection, Object):返回指定集合中指定元素的出现次数
    void copy(List dest, List src):将src中的内容复制到dest中
    boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List对象的所有旧值

    Collection类中提供了多个synchronizedXxx()方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
    (例:synchronizedList(List list),synchronizedMap(Map map))

    
    public class CollectionsTest {
        /*
        Collections:操作Collection、Map的工具类
         */
        @Test
        public void test1() {
            ArrayList list = new ArrayList();
            list.add(123);
            list.add(43);
            list.add(765);
            list.add(-97);
            list.add(0);
    
            System.out.println(list); //[123, 43, 765, -97, 0]
    
            Collections.reverse(list);
            System.out.println(list); //[0, -97, 765, 43, 123]
    
            Collections.shuffle(list);
            System.out.println(list); //[43, 765, 123, 0, -97]
    
            Collections.sort(list);
            System.out.println(list); //[-97, 0, 43, 123, 765]
    
            Collections.swap(list, 0, 1);
            System.out.println(list); //[0, -97, 43, 123, 765]
    
            System.out.println(Collections.max(list)); //765
    
            System.out.println(Collections.min(list)); //-97
        }
    
        @Test
        public void test2() {
            ArrayList list = new ArrayList();
            list.add(123);
            list.add(43);
            list.add(765);
            list.add(765);
            list.add(765);
    
            int frequency = Collections.frequency(list, 765);
            System.out.println(frequency); //3
    
            List dest = Arrays.asList(new Object[list.size()]);
            Collections.copy(dest, list);
            System.out.println(dest); //[123, 43, 765, 765, 765]
    
            //Collection类中提供了多个synchronizedXxx()方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
            //返回的list1即为线程安全的List
            List list1 = Collections.synchronizedList(list);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
  • 相关阅读:
    从零搭建基于SpringCloud Alibaba 鉴权中心服务(详细教程)
    AtCoder Beginner Contest 231(D-F,H)
    Git版本控制管理——Git和GitHub
    Avalonia中的自绘控件
    第4季3:Hi3518e的sensor接口引脚复用设置
    【小程序】页面跳转
    Windows Ubuntu子系统使用USB教程
    键入网址到网页显示,期间发生了什么?
    一个显示图像的Pyside6模板
    基于web在线餐饮网站的设计与实现——仿Coco线上订奶茶饮料6个页面(HTML+CSS+JavaScript)
  • 原文地址:https://blog.csdn.net/m0_61467488/article/details/126111061