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集合的继承结构图解:
面试常见的问题分析
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();
}