JavaSE进阶八 集合二 Map接口

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流一

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容