14_Map集合

Map集合

集合:就是用来存储数据的一个容器(容器的长度会自动扩容)可变长度
Map集合一种比较特殊的容器,<String,Object>-value的结构存储数据(key上面可以存储数据,value上面也可以存储数据)。
Map中的 key 和 value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中

数据库表--->JavaBean 
表名---->类名
字段、列名---->属性名

1.多表查询的时候(查询的数据来自于多个表格)
Student (name,id,...)
Score (score,sid,...)
List<Student>/List<Score>都不行
Map<String,Object>
	字段  , 字段对应的值
1个map集合就可以存储一行多表查询的数据	
	
2.做数据分类的时候
商品分类
Map<String,Object>
	类名名称  分类的数据
等等....

总结:key-value的结构进行存储数据的可变长度的容器。

Map集合的继承结构图解:

day19/images

面试常见的问题分析

1.HashMap的实现原理

HashMap的实现原理:
1.HashMap底层默认是由数组组成初始化容度16,当容度超过75%进行2倍扩容。

2.在HashMap底层数组中存放的是链表,至于存在数组中哪个位置的链表中,是根据存入map集合的key的hashcode值来决定,
  底层对key的hashCode值会进行2次转换得到的hash值对数组的长度求余(取模)得到的值作为数组的下标。(底层求余运算使用的位运算,比除法运算效率更高)
  
3.当数组中某个位置的链表长度大于8 并且数组的长度不小于64的时候,链表结构将会变成红黑树。
  (数组长度小于64的时候 数组+链表  效率 高于 数组+红黑树)

4.当再一次扩容的时候,原先数组位置的存储结构如果是红黑树,但是扩容之后长度少于6那么又会将红黑树转回链表结构。
  数组+链表+红黑树。
  
5.有关hashMap扩容:
    我们都明白扩容因子是0.75,当数组大小为16时,存放12个元素后,再次存放下一个元素时,就需要扩容了。
    这是的12个元素不是指数组16个格子用了12个,而是真的指存放了12个元素,哪怕这12个元素都接在一个链表上。

注:底层对key的hashCode值会进行2次转换得到的hash值对数组的长度求余(取模)得到的值作为数组的下标。

1.调用put方法后,会对key调用hash产生一个哈希值

执行 put() , 该方法会执行 hash(key) 得到 key 对应的 hash 值
算法:h = key.hashCode()) ^ (h >>> 16) 
算数右移16位。此hash值不是hashcode因为做了右移处理。
public V put(K key, V value) {
    //key = "java" value = PRESENT 共享 
    return putVal(hash(key), key, value, false, true); 
}
2.然后对这个哈希值进行与运算,得到坐标
1. 执行构造器 new HashMap() 初始化加载因子 loadfactor = 0.75 HashMap$Node[] table = null

2.执行 put 调用 hash 方法
计算 key 的 hash 值 (h = key.hashCode()) ^ (h >>> 16) 
public V put(K key, V value) {//K = "java" value = 10 
    return putVal(hash(key), key, value, false, true); 
}

3.执行putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
    //定义了辅助变量
    Node<K,V>[] tab; Node<K,V> p; int n, i; 
    
    //table 就是 HashMap  的一个数组,类型是 Node[]
    //if 语句表示如果当前 table 是 null, 或者 大小=0
    //就是第一次扩容,到 16 个空间.
    if ((tab = table) == null || (n = tab.length) == 0) 
        n = (tab = resize()).length;


    (1)根据 key,得到 hash  去计算该 key 应该存放到 table 表的哪个索引位置
    并把这个位置的对象,赋给 p 
    
    (2)判断 p 是否为 null
    (2.1) 如果 p 为 null,表示还没有存放元素,就创建一个 Node(key="java",value=PRESENT) 
    (2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null)
    
    //第二次add直接进入此if
    if ((p = tab[i = (n - 1) & hash]) == null)
        //为什么把key对应的hash存放进去呢?
        //再次存入会查看key对应的hash和下一个相等不相等
        //null表示没有后继结点
        tab[i] = newNode(hash, key, value, null);
    else {
    //第三次add,进入else
    
        //一个开发技巧提示:在需要局部变量(辅助变量)时候,在创建
        Node<K,V> e; K k; 

        如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样
        并且满足 下面两个条件之一:
            (1) 准备加入的 key  和 p  指向的 Node结点的 key  是同一个对象
            (2) p指向的 Node结点的 key 的 equals() 和准备加入的 key 比较后相同
        就不能加入

        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;

        再判断 p 是不是一颗红黑树,
        如果是一颗红黑树,就调用 putTreeVal ,来进行添加
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            如果 table 对应索引位置,已经是一个链表,  就使用 for 循环比较
            (1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
            注意在把元素添加到链表后,立即判断:该链表是否已经达到 8 个结点就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
            
            注意,在转成红黑树时,要进行判断, 判断条件
            if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
                resize();
            如果上面条件成立,先 table 扩容.
 
            只有上面条件不成立时,才进行转成红黑树
 
            (2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接 break

            for (int binCount = 0; ; ++binCount) {
                //如果添加的元素,Set中没有的话,就走这,然后break
                if ((e = p.next) == null) {

                    p.next = newNode(hash, key, value, null);
                    //加入后判断当前链表的个数,是否到达8
                    //达到就调用treeifyBin方法进行红黑树的转换
                    if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st

                        treeifyBin(tab, hash);

                    break;
                }
                //如果已经存在的话,for循环到这直接break
                if (e.hash == hash && 
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                
                p = e;
            }

        }

        if (e != null) { // existing mapping for key
    
            V oldValue = e.value;
    
            if (!onlyIfAbsent || oldValue == null)
                e.value = value; 
            afterNodeAccess(e); 
            return oldValue;
        }
    }
    ++modCount;
    //size 就是我们每加入一个结点 Node(k,v,h,next), size++

    if (++size > threshold)
        resize();//[12-24-48]扩容
        //空方法,留给子类使用。
    afterNodeInsertion(evict); 
    return null;
}

HashMap红黑树转换过程:

有关HashMap的扩容:

1.我们都明白扩容因子是0.75,当数组大小为16时,存放12个元素后,再次存放下一个元素时,就需要扩容了。
2.这是的12个元素不是指数组16个格子用了12个,而是真的指存放了12个元素,哪怕这12个元素都接在一个链表上。

至于什么时候使用红黑树什么时候使用链表,简单来说:

1.当数组的长度小于64时,不使用红黑树,只使用链表,如果某个数组下的结点多了,数组直接扩容就行。
2.当数组的长度大于64时,并且结点数量多于8,就把那个数组下的结构改成红黑树。

我们来看一下源码:
1.

2.如果数组长度小于64,就不使用红黑树,扩容就行了

2. HashMap与Hashtable之间的区别

1.HashMap初始化容度 16 扩容机制超过容度的0.75 进行扩容2倍
  Hashtable初始化容度11 扩容机制超过容度的0.75 进行扩容2倍+1,初始化到第九个元素扩容。
  
2.将数据存入集合的时候
HashMap底层会将key的hashCode进行再一次hash转换。然后再根据hash值对数组的长度求余。
HashTable底层直接将key的hashCode值与数组的长度求余得到最终数组的下标

3.HashMap线程不安全,Hashtable是线程安全的

4.HashMap允许key value为null (都是放在数组的第一位)
  HashTable不允许key value为null
  
5.HashMap父类是AbstractMap
  Hashtable父类是Dictionary

Map集合的应用

1.HashMap应用

public static void main(String[] args) {
		//1.key:存储学号   value 学生姓名
		HashMap<String, String> map=new HashMap<>();
		//2.往map集合中存放数据 (key唯一,无序)  添加,插入
		map.put("S001", "jack");
		map.put("S001", "jack");
		map.put("S001", "jack");
		map.put("S002", "rouse");
		map.put("S003", "marry");
		map.put("S004", "tina");
		map.put("S005", "tom");
		map.put("S006", "jam");
		map.put("S007", "jack");
		System.out.println("---------------删除之前:");
		System.out.println(map);
		//3.判断key是否存在
		System.out.println(map.containsKey("S001")?"key存在":"key不存在");
		//4.根据key删除元素
		map.remove("S001");
		System.out.println("----------------删除之后:");
		System.out.println(map);
		//5.根据key获取value值 (如果可以不存在返回null)
		System.out.println(map.get("S001")+","+map.get("S002"));
		//6.获取所有的key 返回一个Set集合(Key的特点与Set集合特点一样)
		Set<String> set=map.keySet();
		System.out.println("map集合的key为:"+set);
		
		//7.map集合中将Key和value封装到一个对象中Entry<Key,Value> Key,Value
		//将整个map集合转成entry<>对象类型的Set集合
		Set<Entry<String, String>> entrySet=map.entrySet();
		System.out.println(entrySet);
		//8.获取map的长度
		System.out.println(map.size());
		
		//9.遍历map集合 (遍历key,然后通过key获取对应的value值) Set集合只是这个set集合只存了全部的key
		System.out.println("---------------------增强for循环遍历key");
		for(String key:map.keySet()) {
			String value=map.get(key);
			System.out.println(key+"="+value);
		}
		System.out.println("---------------------迭代器循环遍历key");
		Iterator<String> it=map.keySet().iterator();
		while(it.hasNext()) {
			String key=it.next();
			String value=map.get(key);
			System.out.println(key+"="+value);
		}
		//10.将map集合转成Set<Entry<k,v>>集合 遍历Set集合等价于遍历了map集合 set即存了key也存了value
		System.out.println("---------------------增强for循环遍历entry");
		for(Entry<String, String> entry:map.entrySet()) {
			String key=entry.getKey();//得到key
			String value=entry.getValue();//得到value
			System.out.println(key+"="+value);
		}
		System.out.println("---------------------迭代器循环遍历entry");
		Iterator<Entry<String, String>> it1=map.entrySet().iterator();
		while(it1.hasNext()) {
			Entry<String, String> entry=it1.next();
			String key=entry.getKey();
			String value=entry.getValue();
			System.out.println(key+"="+value);
		}
	}  

2. LinkedHashMap的应用

与HashMap方法基本相同只是会保持照添加的顺序。
 public static void main(String[] args) {
	//1.key:存储学号   value 学生姓名
	LinkedHashMap<String, String> map=new LinkedHashMap<>();
	//2.往map集合中存放数据 (key唯一,无序)  添加,插入
	map.put("S001", "jack");
	map.put("S001", "jack");
	map.put("S001", "jack");
	map.put("S002", "rouse");
	map.put("S003", "marry");
	map.put("S004", "tina");
	map.put("S005", "tom");
	map.put("S006", "jam");
	map.put("S007", "jack");
	System.out.println("---------------删除之前:");
	System.out.println(map);
	//3.判断key是否存在
	System.out.println(map.containsKey("S001")?"key存在":"key不存在");
	//4.根据key删除元素
	map.remove("S001");
	System.out.println("----------------删除之后:");
	System.out.println(map);
	//5.根据key获取value值 (如果可以不存在返回null)
	System.out.println(map.get("S001")+","+map.get("S002"));
	//6.获取所有的key 返回一个Set集合(Key的特点与Set集合特点一样)
	Set<String> set=map.keySet();
	System.out.println("map集合的key为:"+set);
	
	//7.map集合中将Key和value封装到一个对象中Entry<Key,Value> Key,Value
	//将整个map集合转成entry<>对象类型的Set集合
	Set<Entry<String, String>> entrySet=map.entrySet();
	System.out.println(entrySet);
	//8.获取map的长度
	System.out.println(map.size());
	
	//9.遍历map集合 (遍历key,然后通过key获取对应的value值) Set集合只是这个set集合只存了全部的key
	System.out.println("---------------------增强for循环遍历key");
	for(String key:map.keySet()) {
		String value=map.get(key);
		System.out.println(key+"="+value);
	}
	System.out.println("---------------------迭代器循环遍历key");
	Iterator<String> it=map.keySet().iterator();
	while(it.hasNext()) {
		String key=it.next();
		String value=map.get(key);
		System.out.println(key+"="+value);
	}
	//10.将map集合转成Set<Entry<k,v>>集合 遍历Set集合等价于遍历了map集合 set即存了key也存了value
	System.out.println("---------------------增强for循环遍历entry");
	for(Entry<String, String> entry:map.entrySet()) {
		String key=entry.getKey();//得到key
		String value=entry.getValue();//得到value
		System.out.println(key+"="+value);
	}
	System.out.println("---------------------迭代器循环遍历entry");
	Iterator<Entry<String, String>> it1=map.entrySet().iterator();
	while(it1.hasNext()) {
		Entry<String, String> entry=it1.next();
		String key=entry.getKey();
		String value=entry.getValue();
		System.out.println(key+"="+value);
	}
}   

3. TreeMap 的应用

public static void main(String[] args) {
	//1.key:存储学号   value 学生姓名
	TreeMap<String, String> map=new TreeMap<>();
	//2.往map集合中存放数据 (key唯一,无序)  添加,插入
	map.put("S001", "jack");
	map.put("S001", "jack");
	map.put("S001", "jack");
	map.put("S002", "rouse");
	map.put("S003", "marry");
	map.put("S004", "tina");
	map.put("S005", "tom");
	map.put("S006", "jam");
	map.put("S007", "jack");
	System.out.println("---------------删除之前:");
	System.out.println(map);
	//3.判断key是否存在
	System.out.println(map.containsKey("S001")?"key存在":"key不存在");
	//4.根据key删除元素
	map.remove("S001");
	System.out.println("----------------删除之后:");
	System.out.println(map);
	//5.根据key获取value值 (如果可以不存在返回null)
	System.out.println(map.get("S001")+","+map.get("S002"));
	//6.获取所有的key 返回一个Set集合(Key的特点与Set集合特点一样)
	Set<String> set=map.keySet();
	System.out.println("map集合的key为:"+set);
	
	//7.map集合中将Key和value封装到一个对象中Entry<Key,Value> Key,Value
	//将整个map集合转成entry<>对象类型的Set集合
	Set<Entry<String, String>> entrySet=map.entrySet();
	System.out.println(entrySet);
	//8.获取map的长度
	System.out.println(map.size());
	
	//9.遍历map集合 (遍历key,然后通过key获取对应的value值) Set集合只是这个set集合只存了全部的key
	System.out.println("---------------------增强for循环遍历key");
	for(String key:map.keySet()) {
		String value=map.get(key);
		System.out.println(key+"="+value);
	}
	System.out.println("---------------------迭代器循环遍历key");
	Iterator<String> it=map.keySet().iterator();
	while(it.hasNext()) {
		String key=it.next();
		String value=map.get(key);
		System.out.println(key+"="+value);
	}
	//10.将map集合转成Set<Entry<k,v>>集合 遍历Set集合等价于遍历了map集合 set即存了key也存了value
	System.out.println("---------------------增强for循环遍历entry");
	for(Entry<String, String> entry:map.entrySet()) {
		String key=entry.getKey();//得到key
		String value=entry.getValue();//得到value
		System.out.println(key+"="+value);
	}
	System.out.println("---------------------迭代器循环遍历entry");
	Iterator<Entry<String, String>> it1=map.entrySet().iterator();
	while(it1.hasNext()) {
		Entry<String, String> entry=it1.next();
		String key=entry.getKey();
		String value=entry.getValue();
		System.out.println(key+"="+value);
	}
}  

源码:

1. 构造器. 把传入的实现了 Comparator 接口的匿名内部类(对象),传给给 TreeMap 的 comparator 
public TreeMap(Comparator<? super K> comparator) {
  this.comparator = comparator; 
}

2. 调用 put 方法
2.1 第一次添加, 把 k-v 封装到 Entry 对象,放入 root 
Entry<K,V> t = root; 
if (t == null) {
  compare(key, key); // type (and possibly null) check
  root = new Entry<>(key, value, null); 
  size = 1; 
  modCount++; 
  return null;
} 

2.2 以后添加 
Comparator<? super K> cpr = comparator; 
if (cpr != null) {
	do { //遍历所有的 key , 给当前 key 找到适当位置 
    parent = t; 
    //动态绑定到我们的匿名内部类的 compare
    cmp = cpr.compare(key, t.key);
		if (cmp < 0)
			t = t.left; 
    else if (cmp > 0)
			t = t.right; 
    else 
		//如果遍历过程中,发现准备添加 Key 和当前已有的 Key 相等,就不添加
			return t.setValue(value); 
  } while (t != null);
}

map集合常见练习题:

练习1:去重加统计以及排序一

<1>.控制台输入一个字符串,使用map集合计算这个字符串由多少种字符组成,以及各个字符出现的次数,并且要求按照出现的次数顺序排列,如果次数一致,要求按照符号的大小进行顺序排列。
代码实现:
public static void main(String[] args) {
	String str="abccbaaaabbc1112231123";
	//1.key用来保存字符(自动去重)   value保存这个符号出现的次数
	LinkedHashMap<Character,Integer> map=new LinkedHashMap<>();
	//2.将字符串的符号一个一个取出来
	for(int i=0;i<str.length();i++) {
		//3.先判断这个符号在map集合中是否存在 存在 则在原来的基础之上加1 不存在 放入集合 并且记次数为1
		char key=str.charAt(i);
		if(map.containsKey(key)) {//key存在 ,在原来的基础之上加1
			map.put(key, map.get(key)+1);
		}else {//不存在 将符号放入集合 并且记次数为1
			map.put(key, 1);
		}
	}
	System.out.println(str+"字符串由"+map.size()+"种符号组成...");
	for(Entry<Character,Integer> entry:map.entrySet()){
		Character key=entry.getKey();
		Integer value=entry.getValue();
		System.out.println(key+"出现的次数为:"+value+"次");
	}
	//4.排序怎么排? 根据次数以及符号进行顺序排列
	Set<Entry<Character, Integer>> set=map.entrySet();
	//5.将set集合转成list集合
	List<Entry<Character, Integer>> list=new ArrayList<>(set);
	Collections.sort(list,new Comparator<Entry<Character, Integer>>() {
		@Override
		public int compare(Entry<Character, Integer> o1, Entry<Character, Integer> o2) {
			if(o1.getValue()==o2.getValue()) {
				return o1.getKey()-o2.getKey();
			}
			return o1.getValue()-o2.getValue();
		}
	});
	System.out.println(list);
	
}

练习2:计算地铁票价

比如2号线
1:广兰路
2:金科路
3:张江高科
4:龙阳路
5:世纪公园
6:上海科技馆
7:世纪大道
8:东昌路
9:陆家嘴
10:南京东路
------------------------------------------------------
计费规则:
3站以内(包含3站) 3块钱
3-5之间(包含5)  4块钱
5站之后每多一站额外多少 2块钱
地铁费用不超过10元。。。
-------------------------------------------------------
输入上车的地点:南京东路

输入下次地点:龙阳路
-------------------------------------------------------
public static void main(String[] args) {
	Scanner sc=new Scanner(System.in);
	String[] positions= {"广兰路","金科路","张江高科","龙阳路","世纪公园","上海科技馆","世纪大道","东昌路","陆家嘴","南京东路"};
	LinkedHashMap<String, Integer> map=new LinkedHashMap<>();
	for(int i=0;i<positions.length;i++) {
		map.put(positions[i], i+1);
	}
	System.out.println("地铁线路为:"+map);
	int startIndex=0;
	String startPosition=null;
	int endIndex=0;
	String endPosition=null;
	while(true) {
		System.out.println("请输入上车地点:");
		startPosition=sc.next();
		if(map.containsKey(startPosition)) {//上车地点存在...
			startIndex=map.get(startPosition);
			break;
		}else {
			System.out.println("您输入的上车地点不存在...");
		}
	}
	System.out.println("上车地点为:"+startPosition);
	
	while(true) {
		System.out.println("请输入下车地点:");
		endPosition=sc.next();
		if(endPosition.equals(startPosition)) {
			System.out.println("重点站不能与起点站相同....");
		}else if(map.containsKey(endPosition)){
			endIndex=map.get(endPosition);
			break;
		}else {
			System.out.println("您输入的下车地点不存在...");
		}
	}
	System.out.println("下车地点为:"+endPosition);
	int step=Math.abs(startIndex-endIndex);//起点站与终点站做差值
	int money=0;
	if(step<=3) {
		money=3;
	}else if(step<=5) {
		money=4;
	}else {
		money=4+(step-5)*1;
		if(money>10) {
			money=10;
		}
	}
	
	System.out.println("总共做了"+step+"站,一共花了"+money+"元");
	sc.close();
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值