查找:
- 根据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
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.测试