Map接口概述

Map:双列集合类的根接口,用于存储具有键(Key)、值(Value)映射关系的元素,每个元素都包含一对键值,在使用Map集合时可以通过指定的Key找到对应的Value,例如根据一个学生的学号就可以找到对应的学生。Map接口的主要实现类有HashMap和TreeMap。

将键映射到值的对象,一个映射不能包含重复的键,每个键最多只能映射到一个值。其实Map集合中存储的就是键值对。map集合中必须保证键的唯一性。

Map接口和Collection接口的不同

  • Map是双列的,Collection是单列的
  • Map的键唯一,Collection的子体系Set是唯一的
  • Map集合的数据结构值针对键有效,跟值无关
  • Collection集合的数据结构是针对元素有效

Map常用的子类:

  • Hashtable:内部结构是哈希表,是同步的。不允许null作为键,null作为值。
  • Properties:用来存储键值对型的配置文件的信息,可以和IO技术相结合。
  • HashMap:内部结构式哈希表,不是同步的。允许null作为键,null作为值。
  • TreeMap:内部结构式二叉树,不是同步的。可以对Map结合中的键进行排序。
  • HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。

Map接口常用方法

方法声明

功能描述

put(K key, V value)

有添加和替换功能

putAll(Map m)

添加一个Map的元素

clear()

清空集合

remove(Object key)

根据键删除一个元素

containsKey()

判断集合是否包含指定的键

containsValue()

判断集合是否包含指定的值

isEmpty()

判断集合是否为空

get(Object key)

根据键获取值

keySet()

获取所有的键

values()

获取所有的值

entrySet()

获取所有的Entry

size()

获取集合元素的个数

 

HashMap

键是哈希表结构,可以保证键的唯一性

LinkedHashMap

Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。

TreeMap

键是红黑树结构,可以保证键的排序和唯一性,自然排序,比较器排序。

package com.lcy.javaMap;
 
import java.util.TreeMap;
//键不可以重复,值可以重复    键可以排序,有排序要注意键的类型 <String>要实现Comparable接口
public class TreeMapDemo {
    public static void main(String[] args) {
        TreeMap<String,String>a=new TreeMap();
        a.put("a", "aa");
        a.put("g", "gg");
        a.put("b", "bb");
        a.put("c", "cc");
        a.put("e", "ee");
        a.put("d", "tt");
 
        System.out.println(a);
     // 输出结果:{a=aa, b=bb, c=cc, d=tt, e=ee, g=gg}
 
    }
}

Hashtable

package com.lcy.javaMap;
 
import java.util.Hashtable;
//键不能重复,值可以重复  无序 是线程安全的
public class HashtableDemo {
    public static void main(String[] args) {
        Hashtable <String,String>s=new Hashtable();
        s.put("a", "aa");
        s.put("v", "vv");
        s.put("l", "ll");
        //s.put(null, "tt");//键和值都不能为null  NullPointerException空指针异常
        System.out.println(s);
       // 输出结果:{l=ll, a=aa, v=vv}
 
    }
}

Map集合遍历

方式1:根据键找值。获取所有键的集合,遍历键的集合,获取到每一个键,根据键找值。

package cn.itcast;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/*
 * Map集合的遍历。
 * Map -- 夫妻对
 * 思路:
 *         A:把所有的丈夫给集中起来。
 *         B:遍历丈夫的集合,获取得到每一个丈夫。
 *         C:让丈夫去找自己的妻子。
 * 
 * 转换:
 *         A:获取所有的键
 *         B:遍历键的集合,获取得到每一个键
 *         C:根据键去找值
 */
public class MapDemo {
    public static void main(String[] args) {
        // 创建集合对象
        Map<String, String> map = new HashMap<String, String>();
        // 创建元素并添加到集合
        map.put("杨过", "小龙女");
        map.put("郭靖", "黄蓉");
        map.put("杨康", "穆念慈");
        map.put("陈玄风", "梅超风");
        // 遍历
        // 获取所有的键
        Set<String> set = map.keySet();
        // 遍历键的集合,获取得到每一个键
        for (String key : set) {
            // 根据键去找值
            String value = map.get(key);
            System.out.println(key + "---" + value);
        }
    }
}

方式2:根据键值对对象找键和值。

  • 获取所有键值对对象的集合
  • 遍历键值对对象的集合,获取到每一个键值对对象
  • 根据键值对对象找键和值
package cn.itcast;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/*
 * Map集合的遍历。
 * Map -- 夫妻对
 * 
 * 思路:
 *         A:获取所有结婚证的集合
 *         B:遍历结婚证的集合,得到每一个结婚证
 *         C:根据结婚证获取丈夫和妻子
 * 
 * 转换:
 *         A:获取所有键值对对象的集合
 *         B:遍历键值对对象的集合,得到每一个键值对对象
 *         C:根据键值对对象获取键和值
 * 
 * 这里面最麻烦的就是键值对对象如何表示呢?
 * 看看我们开始的一个方法:
 *         Set<Map.Entry<K,V>> entrySet():返回的是键值对对象的集合
 */
public class MapDemo {
    public static void main(String[] args) {
        // 创建集合对象
        Map<String, String> map = new HashMap<String, String>();
        // 创建元素并添加到集合
        map.put("杨过", "小龙女");
        map.put("郭靖", "黄蓉");
        map.put("杨康", "穆念慈");
        map.put("陈玄风", "梅超风");
        // 获取所有键值对对象的集合
        Set<Map.Entry<String, String>> set = map.entrySet();
        // 遍历键值对对象的集合,得到每一个键值对对象
        for (Map.Entry<String, String> me : set) {
            // 根据键值对对象获取键和值
            String key = me.getKey();
            String value = me.getValue();
            System.out.println(key + "---" + value);
        }
    }
}

Map集合的应用及扩展

package cn.itcast;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;
/*
 * 需求 :"aababcabcdabcde",获取字符串中每一个字母出现的次数要求结果:a(5)b(4)c(3)d(2)e(1)
 * 
 * 分析:
 *         A:定义一个字符串(可以改进为键盘录入)
 *         B:定义一个TreeMap集合
 *             键:Character
 *             值:Integer
 *         C:把字符串转换为字符数组
 *         D:遍历字符数组,得到每一个字符
 *         E:拿刚才得到的字符作为键到集合中去找值,看返回值
 *             是null:说明该键不存在,就把该字符作为键,1作为值存储
 *             不是null:说明该键存在,就把值加1,然后重写存储该键和值
 *         F:定义字符串缓冲区变量
 *         G:遍历集合,得到键和值,进行按照要求拼接
 *         H:把字符串缓冲区转换为字符串输出
 * 
 * 录入:linqingxia
 * 结果:result:a(1)g(1)i(3)l(1)n(2)q(1)x(1)
 */
public class TreeMapDemo {
    public static void main(String[] args) {
        // 定义一个字符串(可以改进为键盘录入)
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串:");
        String line = sc.nextLine();
        // 定义一个TreeMap集合
        TreeMap<Character, Integer> tm = new TreeMap<Character, Integer>();
        // 把字符串转换为字符数组
        char[] chs = line.toCharArray();
        // 遍历字符数组,得到每一个字符
        for (char ch : chs) {
            // 拿刚才得到的字符作为键到集合中去找值,看返回值
            Integer i = tm.get(ch);
            // 是null:说明该键不存在,就把该字符作为键,1作为值存储
            if (i == null) {
                tm.put(ch, 1);
            } else {
                // 不是null:说明该键存在,就把值加1,然后重写存储该键和值
                i++;
                tm.put(ch, i);
            }
        }
        // 定义字符串缓冲区变量
        StringBuilder sb = new StringBuilder();
        // 遍历集合,得到键和值,进行按照要求拼接
        Set<Character> set = tm.keySet();
        for (Character key : set) {
            Integer value = tm.get(key);
            sb.append(key).append("(").append(value).append(")");
        }
        // 把字符串缓冲区转换为字符串输出
        String result = sb.toString();
        System.out.println("result:" + result);
    }
}

示例2:在很多项目中,应用比较多的是一对多的映射关系,这就可以通过嵌套的形式将多个映射定义到一个大的集合中,并将大的集合分级处理,形成一个体系。

package cn.itcast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
class Student {
    private String name;
    private int age;
    public Student() {
        super();
    }
    public Student(String name, int age) {
        super();
        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;
    }
}
/*
 *         bj    北京校区
 *             jc    基础班
 *                     林青霞        27
 *                     风清扬        30
 *             jy    就业班    
 *                     赵雅芝        28
 *                     武鑫        29
 *         sh    上海校区
 *             jc    基础班
 *                     郭美美        20
 *                     犀利哥        22
 *             jy    就业班    
 *                     罗玉凤        21
 *                     马征        23
 *         gz    广州校区
 *             jc    基础班
 *                     王力宏        30
 *                     李静磊        32
 *             jy    就业班    
 *                     郎朗        31
 *                     柳岩        33
 *         xa    西安校区
 *             jc    基础班
 *                     范冰冰        27
 *                     刘意        30
 *             jy    就业班    
 *                     李冰冰        28
 *                     张志豪        29
 */
public class HashMapDemo {
    public static void main(String[] args) {
        // 创建大集合
        HashMap<String, HashMap<String, ArrayList<Student>>> czbkMap = new HashMap<String, HashMap<String, ArrayList<Student>>>();
        // 北京校区数据
        HashMap<String, ArrayList<Student>> bjCzbkMap = new HashMap<String, ArrayList<Student>>();
        ArrayList<Student> array1 = new ArrayList<Student>();
        Student s1 = new Student("林青霞", 27);
        Student s2 = new Student("风清扬", 30);
        array1.add(s1);
        array1.add(s2);
        ArrayList<Student> array2 = new ArrayList<Student>();
        Student s3 = new Student("赵雅芝", 28);
        Student s4 = new Student("武鑫", 29);
        array2.add(s3);
        array2.add(s4);
        bjCzbkMap.put("基础班", array1);
        bjCzbkMap.put("就业班", array2);
        czbkMap.put("北京校区", bjCzbkMap);
        // 西安校区数据
        HashMap<String, ArrayList<Student>> xaCzbkMap = new HashMap<String, ArrayList<Student>>();
        ArrayList<Student> array3 = new ArrayList<Student>();
        Student s5 = new Student("范冰冰", 27);
        Student s6 = new Student("刘意", 30);
        array3.add(s5);
        array3.add(s6);
        ArrayList<Student> array4 = new ArrayList<Student>();
        Student s7 = new Student("李冰冰", 28);
        Student s8 = new Student("张志豪", 29);
        array4.add(s7);
        array4.add(s8);
        xaCzbkMap.put("基础班", array3);
        xaCzbkMap.put("就业班", array4);
        czbkMap.put("西安校区", xaCzbkMap);
        // 遍历集合
        Set<String> czbkMapSet = czbkMap.keySet();
        for (String czbkMapKey : czbkMapSet) {
            System.out.println(czbkMapKey);
            HashMap<String, ArrayList<Student>> czbkMapValue = czbkMap
                    .get(czbkMapKey);
            Set<String> czbkMapValueSet = czbkMapValue.keySet();
            for (String czbkMapValueKey : czbkMapValueSet) {
                System.out.println("\t" + czbkMapValueKey);
                ArrayList<Student> czbkMapValueValue = czbkMapValue
                        .get(czbkMapValueKey);
                for (Student s : czbkMapValueValue) {
                    System.out.println("\t\t" + s.getName() + "---"
                            + s.getAge());
                }
            }
        }
    }
}

补充知识

1.HashSet中为什么不能存储重复元素,如何判断元素是否重复
在add()的底层方法中,每添加一个元素,就会判断集合中是否包含了此元素

在底层使用hashCode()和equals()方法来判断内容是否重复

hashCode()是Object类中的方法

public native int hashCode();native修饰的方法没有方法体,称为本地方法,java没有实现,是调用操作系统的方法

Object类中的hashCode()方法是用来获取对象在内存中的地址

其他类中重写hashCode()方法是用来根据对象内容计算出来的哈希值(a-->97)

Object类中的equals()是比较两个对象的地址

其他类约定equals()用来比较对象的内容是否相等

equals()效率太低,要把每个位置上的字符进行对比

在判断两个元素是否相同时,先用hashCode()方法比较两个元素内容的哈希值.

但是此种做法并不安全,2个不同的内容也可能算出同样的哈希值(比如说中文里的"通话"和"重地")

这两个元素虽然内容不同,但是算出来的哈希值相同,此时就又调用了equals()方法,来比较它们的内容是否相同。

2.HashMap的底层结构

java map 放到里一个map中_数据结构

3.HashMap中添加元素的过程
根据添加的元素计算出一个hash值(a-->97),再进行一次计算,计算出元素在数组中的位置(例如97%16=1;那么就放在第一个位置)然后继续有元素存储进来,如果存在了相同的位置,将新的元素存入一个之前元素的下一位,当链表长度达到8(并且数组长度大于64时)会把链表转化为红黑树

哈希数组长度默认是16

负载因子是0.75(16*0.75=12,有3/4的数组被存储的时候,哈希数组就会扩容)

哈希数组如果扩容每次扩容到原来的2倍

当链表长度到达8,并且哈希数组长度大于64,链表会转为红黑树