查找:
  • 根据key(关键字)查找相应的value(对应值)
Map API
  • Map属于Java的集合API
  • Map是面向查找而设计的API,查找表。
  • Map API的查找性能非常好
  • Map API提供了根据key查找value的方法
Map接口
  • Map接口定义了根据key查找value的功能,其全部实现类都提供了根据key查找value的功能
  • Map中的key是不可以重复的,value可以重复
  • 每个key对应唯一value
  • 根据key查找唯一value
  • Map常见实现类有两个:
    -HashMap 利用散列表算法实现的Map
    -TreeMap 利用二叉排序树算法实现Map

HashMap

  • 是Map接口的最常用实现类
  • 其内部采用了散列表算法
  • HashMap是计算机中查找速度最快的算法
构造器
Map map = new HashMap();
  • 向map集合中添加数据,数据用于查找功能
    val = map.put(key,value);
eg:
val = map.put("济南","天气热");//第一次添加成功,返回null
val = map.put("济南","历城区");//第二次是替换,返回原有的value"天气热"
从map中查找数据
语法一:
Map<String,String> map = new HashMap();
public class MapDome1 {
    public  static void main(String[] a){
        /*
        ==创建一个HashMap
        向map中添加数据
        从map查找数据**==**
        */
        //创建map集合
        Map map = new HashMap();
        List<String> JN = new ArrayList<>();
        JN.add("好热");
        JN.add("好久不下雨");
        JN.add("艳阳高照,受不了");
        //向map中添加key-value数据
        Object val = map.put("JN","天气好热");
        //第一次添加数据,返回null
        System.out.println(val);
        //第二次替换数据
        val = map.put("JN",JN);
        System.out.println(val);
        System.out.println(map.toString());
        map.put("HF","凉快");
        map.put("BJ","热死");
        val = map.get("JN");
        System.out.println(val);
        System.out.println(map.toString());

    }

}
语法二:
Map<String,List> map = new HashMap();
public class MapDome {
    public static void main(String[] agr){
        Map<String,List> map = new HashMap<>();
        List<String> TJ = new LinkedList<>();
        TJ.add("这里是个好地方");
        TJ.add("微风不燥");
        TJ.add("时光刚好");
        TJ.add("流连忘返");
        //System.out.println(TJ);
        map.put("TJ",TJ);
        List<String> BJ = new LinkedList<>();
        BJ.add("国之重地");
        BJ.add("北方太平");
        BJ.add("雾霾严重");
        BJ.add("肃然起敬");
        map.put("BJ",BJ);
        Scanner cin = new Scanner(System.in);
        System.out.println("请输入想要查看的城市");
        String city = cin.nextLine();
        List<String> see = map.get(city);
        System.out.println(see);
        int num = map.size();
        System.out.println(num);
    }
}

如果希望一个key对应多个Value,则可以利用List作为value存储多个值。

性能测试

  • 相对于 LinkedList HashMap具有极高的查询性能
public class MapTest {
    public static void main(String[] arg){
        //纵向比较 HashMap 不同数据量时候的查询性能
        test(1000000);
        test(10000000);
        //横向比较 HashMap 和 LinkedList的查询性能
        test(10000000);
        Linkedtest(10000000);
        //横向比较 HashMap 和 ArrayList的查询性能
        test(10000000);
        ArrayListtest(10000000);
    }
    public static void test(int n){//写成方法,方便代码复用
        Map<Integer,String> map = new HashMap<>();
        for (int i = 0; i <n; i++) {
            map.put(i,"n"+i);
        }
        long t1 = System.nanoTime();
        String val = map.get(n-1);
        long t2 = System.nanoTime();
        System.out.println("Val:"+val+" , time:"+(t2-t1)+"ns");
    }
    public static void Linkedtest(int n){
        List<String> list = new LinkedList<>();
        for (int i = 0; i <n; i++) {
            list.add("n"+i);
        }
        long t1 = System.nanoTime();
        String str = list.get(n/2);
        long t2 = System.nanoTime();
        System.out.println("LStr:"+str+" , time:"+(t2-t1)+"ns");
    }
    public static void ArrayListtest(int n){
        List<String> list = new ArrayList<>();
        for (int i = 0; i <n; i++) {
            list.add("n"+i);
        }
        long t1 = System.nanoTime();
        String str = list.get(n-1);
        long t2 = System.nanoTime();
        System.out.println("RStr:"+str+" , time:"+(t2-t1)+"ns");
    }
}

HashMap工作原理

  • LinkedList 在查询元素时,采用顺序查找,当被查询元素在头尾时查询速度快。但是当被查询元素处于链表中部时,则会出现顺序查找,此时性能非常差。
  • HashMap 利用散列算法可以直接定位元素位置,所以查找性能优异,因为不是顺序查找,所以散列查找不收数据量的影响。

散列算法的工作原理

  • 在HashMap内部有一个数组用于存储key-Value数据
  • 在添加数据时,根据Key的hashCode()返回值计算在数组的存储位置,将Key-Value数据存储到数组的对应位置
  • 根据Key查找时,根据Key的hashCode()计算数组下标位置,直接定位到数组中Key-Value数据,并返回value值。
HashMap中查找的特点:
  • 不进行顺序查找,直接利用算法定位数组中的下标位置。因为避免了顺序查找,所以查询性能好!
hashCode()方法与散列表
  • Java为了在最底层支持散列表(HashMap 哈希表)算法在Object类上定义了hashCode()方法,用于支持散列表算法。
  • hashCode()方法和equals()是一对方法,需要一同重写!!!!
    ——如果不成对重写这两个方法,会造成HashMap工作故障
  • 如果重写hashCode时要遵守如下规则
    1.当两个对象equals比较相等时,两个对象必须有一样的hashCode值。
    2.当两个对象equals比较不相等时,两个对象的hashCode尽可能不同。
  • Eclipse及其他的开发工具提供了成对生成equals和hashCode方法,利用工具生成即可。
    ——一般按照核心关键属性比较两个对象是否相等。
  • Java的API,包括String、Integer都很好的成对的重写了equals和hashCode。

对象的默认hashCode值不是对象的地址值

HashMap元素添加过程(put)

  • HashMap中有一个数组用于存储key-value数据对
    1.根据key的hashCode值,利用散列算法计算出数组下标位置。
    2.找到数组下标位置

如果位置为null,则直接将key-value存储到数组中

如果位置上已有数据,则调用key的equals方法比较数组中存在key

>如果key相等,则替换对应的value值

  >如果key不等,则找链表中后续的元素并且判断是否相等
  
  >如果key相等,则替换value
  
  >如果不相等则将key-value追加到链表后部

概括:根据key的hashCode计算数组下标位置,再用equals比较ke是否相等,相等就替换,不等就插入。

HashMap元素的查找过程(get)

1.根据key的hashCode值,利用散列算法计算出数组下标位置。

2.找到数组下标位置
>如果位置为null,则直接返回null

>如果位置上已有数据,则调用key的equals方法比较数组中存在key

    >如果key相等,则返回value数据

    >如果key不等,则找链表中后续的元素并且判断是否相等
    
    >如果key相等,则返回value
    
    >如果不相等则返回null

概述:先根据hashCode计算数组下标位置,在根据key的equals比较key,如果找到则返回value,否则返回null。

HashMap相关术语

  • HashMap中保存key-value的数组称为 散列表

HashMap中的数据按照散列值存储和添加顺序无关。但不是一个随机顺序

  • 根据key的hashCode计算数组下标位置的算法称为 散列算法经过散列算法得到的数组下标称为散列值

HashMap中的散列算法利用二进制运算实现的,性能极好。

  • 散列值相同时,key-value存储的链表结构称为散列桶

散列桶中查询是顺序查找,性能不好,原则上要避免散列桶。

  • 当key的hashCode值很分散时候,散列桶会变少。

hashCode的实现规则就是一个分散原则。

  • HashMap++元素和数组容量的比值++称为加载因子,当加载因子小于等于75%的情况下,经过“统计”发现很少出现散列桶,即便出现散列桶也很少超过3层。
  • 当向HashMap中添加数据时,其数据和数组容量的比值大于75%时,HashMap会对数组进行扩容,并重新计算元素的散列值,这个过程称为重新散列

重新散列会影响散列表添加()性能。
散列表提供了重载构造器,可以预先设置数组的容量,避免重新散列,提高性能。HashMap可以在重载构造器中设置数组容量,用于减少散列。

重载构造器语法:
Map<String,String> map = new HashMap<>(n+n/2);
该方法建议在已知数组容量的时候使用

经典面试题

HashMap如何存储的?

概括:根据key的hashCode计算数组下标位置,再用equals比较ke是否相等,相等就替换,不等就插入。
概述:先根据hashCode计算数组下标位置,在根据key的equals比较key,如果找到则返回value,否则返回null。

HashMap 用途

  • HashMap的查找性能非常好,在软件开发中尽量将查找功能交给HashMap完成。
  • 案例, 利用map缓存Http协议头信息:

1.创建文件 headers.txt 保存http协议头信息:

我这里用的是有道云笔记首页的Request Headers

java 后台 Map 接收 java map api_数据

Host: ursdoccdn.nosdn.127.net
Connection: keep-alive
If-None-Match: e36a35c363d892f3c44cfa821427ce36
If-Modified-Since: Mon, 16 Apr 2018 14:57:44 Asia/Shanghai
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/66.0.3359.181 Chrome/66.0.3359.181 Safari/537.36
Accept: */*
Referer: https://note.youdao.com/web/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

2.读取文件内容到Map:

/**
 * 输出全部头信息,利用遍历map可以输出全部信息
 * map没有直接提供遍历接口,map间接提供了遍历功能
 * map.entrySet().iterator()
 */
public class headersDome {
    public static void main(String[] arg){
        try {
            BufferedReader br 
	            = new BufferedReader(
				     new InputStreamReader(
					     new FileInputStream("./headrs.txt")));
            String line = null;
            Map<String,String> map = new HashMap<>();
            while ((line = br.readLine()) != null){
                //line 代表文件中的每一行
                //为了逻辑严禁,跳过可能出现的空行
                if(line.trim().isEmpty()){
                    continue;
                }
                String[] arr = line.split(":\\s");
                map.put(arr[0],arr[1]);

            }
            System.out.println(map);

3.利用map查询一个协议头 信息:

//查询头信息
		String host = map.get("Host");
		System.out.println(host);

4.利用map的变量,显示全部缓存在map中的信息

try {
              Set<Map.Entry<String,String>> entries 
		              = map.entrySet();
                for (Map.Entry<String,String> e:entries) {
         //获取全部Entry的集合,每个entry对象包含两个属性,分别是key和value
         //e 代表map集合中的每个key-value对应e.getKey()方法获取key值
         //e.getValue()方法是获取value值
                    System.out.println(e.getKey()+":"+e.getValue());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            br.close();
        }catch (Exception e){
            System.out.println("文件未找到,请核对文件位置");

        }
    }
}

5.测试