美文网首页
JavaSE进阶八 集合二 Map接口

JavaSE进阶八 集合二 Map接口

作者: SimonLike | 来源:发表于2021-06-15 11:29 被阅读0次

    1,Map接口

    • Map和Collection没有继承关系。
    • Map集合以key和value的方式存储数据:键值对
      • key和value都是引用数据类型。
      • key和value都是存储对象的内存地址。
      • key起主导地位,value是key的一个附属品。

    1.1 java.util.Map接口中常用的方法:

        V put(K key, V value); // 向map集合中添加键值对
        V get(Object key); // 通过key获取value
        int size(); // 获取map集合中键值对的个数
        V remove(Object key); // 通过key删除键值对
        boolean containsKey(Object key); // 判断map中是否包含某个key
        boolean containsValue(Object value);// 判断map中是否包含某个value
        void clear(); // 清空map集合
        boolean isEmpty(); // 判断map集合中的元素个数是否为0
        Collection<V> values(); // 获取集合中所有的value
    
    代码示例
    import java.util.Collection;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.Map;
    
    public class MapTest01 {
        public static void main(String[] args) {
            // 创建map集合对象
            Map<Integer,String> map = new HashMap<>();
            // 向map集合中添加键值对
            map.put(1,"zhangshan");
            map.put(2,"lisi");
            map.put(3,"wangwu");
    
            // 通过key获取value
            String s = map.get(2);
            System.out.println(s);
    
            // 获取键值对的数量
            System.out.println("键值对的数量:" + map.size());
    
            // 通过key删除key-value
            map.remove(2);
    
            // 获取键值对的数量
            System.out.println("键值对的数量:" + map.size());
    
            // 判断是否包含某个key
            // contains方法底层调用的都是equals进行比对的,所以自定义的类型需要重新equals方法。
            System.out.println(map.containsKey(1)); // true
    
            // 判断是否包含某个value
            System.out.println(map.containsValue("wangwu")); // true
    
            // 获取集合中所有的value
            Collection<String> values = map.values();
            for (String ss : values){
                System.out.println(ss);
            }
    
            // 清空集合
            map.clear();
            System.out.println("键值对的数量:" + map.size());
    
            // 判断是否为空
            System.out.println(map.isEmpty());// true
    
        }
    }
    

    1.2 Map集合的遍历

      java.util.Map接口中常用的方法:
      Set<Map.Entry<K, V>> entrySet(); // 把Map集合直接全部转换成Set集合,Set集合中元素的类型是:Map,Entry。
      Set<K> keySet(); // 获取集合所有的key
    
    代码示例
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.Set;
    
    public class MapTest02 {
        public static void main(String[] args) {
            // 创建Map集合对象
            Map<Integer,String> map = new HashMap<>();
    
            // 添加元素
            map.put(1,"zhangsan");
            map.put(2,"lisi");
            map.put(3,"wangwu");
            map.put(4,"zhaosi");
            // 遍历Map集合
            System.out.println("=================================================");
    
            // 方式一:先获取所有的key,通过遍历所有的key,来遍历value
            Set<Integer> set = map.keySet();
    
            // 使用迭代器遍历
            Iterator<Integer> it = set.iterator();
            while (it.hasNext()){
                Integer key = it.next();
                String value = map.get(key);
                System.out.println(key + "=" + value);
            }
            // 使用增强for循环遍历
            for (Integer key : set){
                String value = map.get(key);
                System.out.println(key + "=" + value);
            }
    
            System.out.println("=================================================");
    
            // 方式一:使用 Set<Map.Entry<K, V>> entrySet();
            // 把集合直接全部转换成Set集合
            // Set集合中元素的类型是:Map.Entry
            Set<Map.Entry<Integer,String>> set1 =  map.entrySet();
    
            Iterator<Map.Entry<Integer,String>> it1 = set1.iterator();
            // 使用迭代器遍历
            while (it1.hasNext()){
                Map.Entry<Integer,String> entry = it1.next();
                System.out.println(entry);
                System.out.println("key:" + entry.getKey());
                System.out.println("value:" + entry.getValue());
            }
    
            // 使用增强for循环遍历
            for (Map.Entry<Integer,String> node : set1){
                System.out.println(node);
                System.out.println("key:" + node.getKey());
                System.out.println("value:" + node.getValue());
            }
        }
    }
    

    2,HashMap集合

    • HashMap集合底层是哈希表/散列表的数据结构。

    • HashMap集合的默认初始化容量是16,默认加载因子是0.75。

      • 加载因子:当HashMap集合底层数组的容量达到75%的时候,数组开始扩容。
      • 重点:HashMap集合的默认初始化容量必须是2的倍数,这是官方推荐的;是为了达到散列均匀,提高HashMap集合的存取效率。
    • 哈希表示一个怎么样的数据结构

      • 哈希表是一个数组和单向链表的结合体。
      • 数组:在查询方面效率较高,随机增删方面效率很低。
      • 单向链表:在随机增删方面效率很高,在查询方面效率很低。
      • 哈希表将以上两种数据结构融合在一起,充分发挥它们各自的优点。
    • HashMap集合底层的源代码:

        public class HashMap{
            //  HashMap底层实际上就是一个数组。(一维数组)
            Node<K,V>[] table;
            // 静态内部类
            static class Node<K,V> implements Map.Entry<K,V> {
                final int hash; // 哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换存储数组的下标)
                final K key; // 存储到Map集合中的key
                V value; // 存储到Map集合中的value
                Node<K,V> next; // 下一个节点的内存地址。
            }
        }
      
    • 主要掌握(一定要掌握):

      • map.put(k,v)实现原理:

          1,现将k,v封装到Node对象当中。
          2,底层会调用k的hashCode()方法得出hash值,然后通过哈希函数/算法,将hash值转换成数组的下标,
          下标位置上如果没有任何元素,就把Node添加到这个位置上,如果下标对应的位置上有链表,此时会拿着k
          和链表上的每一个节点中的k进行equals,如果所有的equals方法返回都是false,那么这个新节点将被
          添加到链表的末尾,如果其中有一个equals返回了true,那么这个节点的value将会被覆盖。
        
      • v = map.get(k)实现原理:

          先调用k的hashCode()方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个
          位置上,如果这个位置上什么也没有,返回null;如果这个位置上有单向链表,那么会拿着参数k和单向链
          表上的每个节点中的k进行equals,如果所以equals方法返回false,那么get方法返回null,只要有一
          个节点的k和参数k equals的时候返回true;那么此时这个节点的value就是我们要找的value,get方
          法最终返回这个要找得value。
        
    • HashMap集合的key部分特点:

      • 无序,不可重复。
      • 无序:不一定挂到哪个单向链表上。
      • 不可重复:equals方法保证HashMap集合的key不可重复;如果key重复value覆盖。
    • 哈希表HashMap使用不当时无法发挥性能

      • 假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表,这种情况称为:散列分布不均匀。
      • 什么是散列分布均匀?
        • 假设有100个元素,10个单向链表,每个单向链表上有10个节点,这是最好的,是散列分布均匀的。
      • 假设将所有的hashCode()方法返回值都设定为不一样的值可以吗?
        • 不可以,这样会导致底层哈希表变成一维数组,没有链表的概念了;也是散列分布不均匀。
    代码示例
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    
    public class HashMapTest01 {
        public static void main(String[] args) {
            // 测试HashMap集合key部分元素的特点
            // Integer是key,hashCode()和equals()都重写了
            Map<Integer,String> map = new HashMap<>();
            map.put(11,"张三");
            map.put(12,"李四");
            map.put(13,"王二");
            map.put(14,"麻子");
            map.put(14,"小红"); // key值重复,value会自动覆盖
    
            System.out.println(map.size());
    
            // 遍历集合
            Set<Map.Entry<Integer,String>> set = map.entrySet();
    
            for (Map.Entry<Integer,String> entry : set){
                System.out.println(entry);
                System.out.println("key:" + entry.getKey());
                System.out.println("value:" + entry.getValue());
            }
    
        }
    }
    

    3,HashSet集合

    • HashSet集合是一个哈希表数据结构。

    • HashSet集合初始化容量是16(初始化容量建议是2的倍数);扩容:原容量的2倍。

    • 无序不可重复

      • 1,存储顺序和取出顺序不同。
      • 2,不可重复。
      • 3,放到HashSet集合中的元素实际上是放到HashMap集合的key部分了。
    • 向Map集合中存取元素时,先调用key的hashCode()方法,然后再调用equals方法;equals方法有可能调用,也有可能不调用。

      • 拿put(k,v)举例,什么时候equals不调用?

        • k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标;
          如果下标位置上是null,equals方法不需要执行。
      • 拿get(k)举例,什么时候equals不调用?

        • 如果单向链表上只有一个元素时,不需要调用equals方法。
    • 重点:放在HashMap集合key部分元素(在HashSet集合中),以及放到HashSet集合中的元素;需要同时重写hashCode()和equals()方法。

    代码示例
    import java.util.HashSet;
    import java.util.Objects;
    import java.util.Set;
    
    public class HashMapTest02 {
        public static void main(String[] args) {
            Student s1 = new Student("zhangsan");
            Student s2 = new Student("zhangsan");
    
            System.out.println(s1.equals(s2));// 重写equals方法之前:false  之后:true
    
            System.out.println("s1的hashCode: " + s1.hashCode()); // 重写hashCode方法之前:2129789493  之后:-1432604525
            System.out.println("s2的hashCode: " + s2.hashCode()); // 重写hashCode方法之前:1313922862  之后:-1432604525
    
            //
            Set<Student> students = new HashSet<>();
            students.add(s1);
            students.add(s2);
    
            System.out.println(students.size()); // 重写hashCode方法之前:2  之后:1
        }
    }
    
    class Student{
        private String name;
    
        public Student() {
        }
    
        public Student(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        // 重写equals方法
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Student student = (Student) o;
            return Objects.equals(name, student.name);
        }
        // 重写hashCode方法
        @Override
        public int hashCode() {
            return Objects.hash(name);
        }
    }
    

    4,Hashtable集合

    • HashMap集合允许key为null,但是key值null只能有一个。

      • HashMap的key和value都是可以为null的。
    • Hashtable的key可以为null吗?

      • Hashtable的key和value都不能为null的。
    • Hashtable方法都带有synchronized,是线程安全的,
      Hashtable对线程的处理导致效率较低,使用比较少(线程安全有其他的方案)。

    • Hashtable和HashMap一样,底层都是哈希表数据结构。

    • Hashtable的初始化容量是11,默认加载因子:0.75f。

    • Hashtable的扩容是:原容量 * 2 + 1

    代码示例
    import java.util.HashMap;
    import java.util.Hashtable;
    import java.util.Map;
    
    public class HashMapTest03 {
        public static void main(String[] args) {
            Map map = new HashMap();
            // HashMap集合允许key为null
            map.put(null,null);
            System.out.println(map.size());// 1
    
            // key重复value被覆盖
            map.put(null,"abc");
            System.out.println(map.size());// 1
    
            // 通过key获取value
            System.out.println(map.get(null));// abc
    
            // ==========================================================
            Map map1 = new Hashtable();
            map1.put(null,null); // 运行时会报空指针异常
    
        }
    }
    

    5,了解Propertie属性对象

    • 目前只需要掌握Properties属性对象的相关方法即可。
    • Properties是一个Map集合,继承Hashtable;Properties的key值和value都是String类型。
    • Properties被称为属性对象。
    • Properties是线程安全的
    代码示例
    import java.util.Properties;
    
    public class PropertiesTest01 {
        public static void main(String[] args) {
            // 创建一个Properties对象
            Properties properties = new Properties();
            // 添加存储元素
            properties.setProperty("name","zhangsan");
            properties.setProperty("password","123");
    
            System.out.println(properties.size());
    
            // 通过key获取value
            System.out.println(properties.getProperty("name"));
            System.out.println(properties.getProperty("password"));
    
        }
    }
    

    6,TreeSet集合

    • TreeSet集合底层实际上是一个TreeMap,TreeMap集合底层是一个二叉树。
    • 放到TreeSet集合中的元素,等同于放到TreeMap集合的key部分。
    • TreeSet集合存储元素特点:
      • 1,无序不可重复,但存储的元素可以自动按照大小顺序排序!称为:可排序集合。
      • 2,无序:指的是存进去的顺序和取出来的顺序不同,并且没有下标。
    代码示例
    import java.util.Set;
    import java.util.TreeSet;
    
    public class TreeSetTest01 {
        public static void main(String[] args) {
            // 创建一个TreeSet集合
            TreeSet<String> set = new TreeSet<>();
            set.add("a");
            set.add("d");
            set.add("c");
            set.add("e");
            set.add("c");
            set.add("b");
    
            System.out.println(set.size()); // 5  不可重复
            for (String s : set){
                System.out.println(s); // 输出会从小到大自动排序(升序)
            }
        }
    }
    

    6.1, 自平衡二叉树

    • TreeSet/TreeMap集合是自平衡二叉树,遵循左小右大原则排序。

    • 遍历二叉树的时候有三种方式:

        前序遍历:跟左右
        中序遍历:左跟右
        后序遍历:左右跟
        前中后说的是"跟"的位置,跟在前面是前序,跟在中间是中序,跟在后面是后序。
      
    • TreeSet/TreeMap集合采用的是:中序遍历方式。

    • Iterator迭代器采用的是:中序遍历方式。

    7, TreeSet集合元素的排序

    • 放到TreeSet集合和TreeMap集合key部分的元素想要排序,有两种方式:
      • 1,放在集合中的元素实现java.lang.Comparable接口。
      • 2,在构造TreeSet或TreeMap集合的时候给它传一个比较器对象。
    • Comparable和Comparator怎么选择?
      • 当比较规则不会改变的时候,或当比较规则只有1个的时候,建议使用Comparable接口。
      • 当比较规则有多个,并且需要在多个比较规则之间频繁切换,建议使用Comparator接口。(Comparator接口符合OCP原则)

    7.1,第一种排序方式(Comparable接口)

    自定义类型使用TreeSet集合进行排序,需要实现java.lang.Comparable接口;以及重写compareTo方法,进行规则比较。

    import java.util.TreeSet;
    
    public class TreeSetTest02 {
        public static void main(String[] args) {
            // 自定义类型使用TreeSet集合进行排序,需要重写compareTo方法,进行规则比较。
            //
            Customer c1 = new Customer(23);
            Customer c2 = new Customer(20);
            Customer c3 = new Customer(28);
            Customer c4 = new Customer(25);
    
            // 创建TreeSet集合
            TreeSet<Customer> customers = new TreeSet<>();
            //添加元素
            customers.add(c1);
            customers.add(c2);
            customers.add(c3);
            customers.add(c4);
    
            // 遍历
            for (Customer c : customers){
                System.out.println(c);
            }
        }
    }
    
    // 放在TreeSet集合中的元素需要实现java.lang.Comparable接口
    // 并且实现compareTo方法,equals可以不写。
    class Customer implements Comparable<Customer> {
        int age;
    
        public Customer() {
        }
    
        public Customer(int age) {
            this.age = age;
        }
    
        /*
            重写compareTo方法
            需要在这个方法中编写比较逻辑,或者说比较规则,按照什么进行比较!
            k.compareTo(t.key)
            拿参数k和集合中的每一个k进行比较,返回值:>0 =0 <0
            比较规则由程序员指定:按年龄升序、降序等...
         */
        @Override
        public int compareTo(Customer o) {
            return this.age - o.age;
        }
    
        // 重写toString方法
        @Override
        public String toString() {
            return "Customer{" +
                    "age=" + age +
                    '}';
        }
    }
    

    7.2,第二种排序方式(Comparator接口)

    自定义类型使用TreeSet集合进行排序,需要实现java.util.Comparator接口;在构造TreeSet或TreeMap集合的时候给它传一个比较器对象。
    import java.util.Comparator;
    import java.util.TreeSet;
    
    public class TreeSetTest03 {
        public static void main(String[] args) {
            // 创建TreeSet集合的时候,需要使用比较器
            // TreeSet<WuGui> treeSet = new TreeSet<>(); //这样创建没有通过构造方法传递一个比较器进去是不行的。
    
            // 使用匿名内部类实现比较器(类没有名字,直接new接口)
            // TreeSet<WuGui> treeSet = new TreeSet<>(new Comparator<WuGui>() {
            //     @Override
            //     public int compare(WuGui o1, WuGui o2) {
            //         return o1.age - o2.age;
            //     }
            // });
    
            TreeSet<WuGui> treeSet = new TreeSet<>(new WuGuiComparator());
            treeSet.add(new WuGui(1000));
            treeSet.add(new WuGui(600));
            treeSet.add(new WuGui(900));
            treeSet.add(new WuGui(2000));
    
            for (WuGui wg : treeSet){
                System.out.println(wg);
            }
        }
    }
    
    class WuGui{
        int age;
    
        public WuGui(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "WuGui{" +
                    "age=" + age +
                    '}';
        }
    }
    // 单独定义一个比较器
    // 实现java.util.Comparator接口。
    
    class WuGuiComparator implements Comparator<WuGui>{
        @Override
        public int compare(WuGui o1, WuGui o2) {
            // 比较规则:按年龄排序
            return o1.age - o2.age;
        }
    }
    

    上篇:JavaSE进阶八 集合一 Collection接口
    下篇:JavaSE进阶九 IO流一

    相关文章

      网友评论

          本文标题:JavaSE进阶八 集合二 Map接口

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