Map接口概述
(1)该集合存储键值相对。一对一对往里存。而且要保证键的唯一性。
(2)将键映射到值的对象
(3)一个映射不能包含重复的键
(4)每个键最多只能映射到一个值
Map集合的功能概述
a:添加功能
* V put(K key,V value):添加元素。
* 如果键是第一次存储,就直接存储元素,返回null
* 如果键不是第一次存在,就用值把以前的值替换掉,返回以前的值
b:删除功能
* void clear():移除所有的键值对元素
* V remove(Object key):根据键删除键值对元素,并把值返回
c:判断功能
* boolean containsKey(Object key):判断集合是否包含指定的键
* boolean containsValue(Object value):判断集合是否包含指定的值
* boolean isEmpty():判断集合是否为空
d:获取功能
* Set<Map.Entry<K,V>> entrySet():
* V get(Object key):根据键获取值
* Set<K> keySet():获取集合中所有键的集合
* Collection<V> values():获取集合中所有值的集合
e:长度功能
* int size():返回集合中的键值对的个数
package com.map;
import java.util.*;
public class mapDemo
{
public static void main(String[] args)
{
Map<String , String> map = new HashMap<String , String>(); //HashMap可以存空 Hashtable不能
map.put("01","zhangshang01"); //键必须唯一,键相同则覆盖
map.put("02","zhangshang02");
map.put("03","zhangshang03");
System.out.println("MapcontainsKey="+map.containsKey("02"));
System.out.println("get="+map.get("01")); //根据键获取值
Collection<String> coll = map.values(); //获取map集合中所有的值
System.out.println(coll);
System.out.println(map);
}
}
输出
Map接口和Collection接口的不同
* Map是双列的,Collection是单列的* Map的键唯一,Collection的子体系Set是唯一的
* Map集合的数据结构值针对键有效,跟值无关;Collection集合的数据结构是针对元素有效
HashMap
HashMap的底层是用Hash数组和单向链表实现的,当调用put方法时,首先计算key的hashcode,如果key值对应的hashcode相同,则会使用equals函数比较key,如果返回true,则新的value值覆盖原来的值;如果返回false,则插入单向链表的头部。也就是说数组中存储的是最后插入的元素。
HashMap的两个重要属性是容量capacity和加载因子loadfactor,默认值分布为16和0.75,当容器中的元素个数大于capacity*loadfactor时,容器会进行扩容resize 为2n,在初始化Hashmap时可以对着两个值进行修改,负载因子0.75被证明为是性能比较好的取值,通常不会修改,那么只有初始容量capacity会导致频繁的扩容行为,这是非常耗费资源的操作,所以,如果事先能估算出容器所要存储的元素数量,最好在初始化时修改默认容量capacity,以防止频繁的resize操作影响性能。
HashMap和HashSet 的底层算法都是相同的。这里需要明确说明的是,HashSet底层依赖的是HashMap。HashSet底层实际是双列集合,只不过隐藏了一列,这里看看HashSet 添加方法的源码
可以看到HashSet的添加方法实际调用的是map的添加方法,PRESENT是一个用final修饰的Object,直接创建一个Object对象放在值得位置,但是值得对象不显示。源码如下图
为什么用单列集合底层依赖双列集合呢?
这样他们俩都可以依赖相同的Hash算法,由双列隐藏掉一列比较容易,但是想由一列变成双列就比较困难。所以选择将HashSet的value值隐藏起来。
Map集合的两种取出方式:
1,KeySet:将map中所有的键存入到Set集合,因为Set集合具备迭代器。所以可以迭代方式去除所有的键,在根据get方法。获取每一个键对应的值。
Map集合取出原理:将map集合转成set集合。在通过迭代器取出。
import java.util.*;
class MapDemo {
public static void main(String[] args) {
Map<String , String> map = new HashMap<String , String>();
map.put("01","zhangshang01");
map.put("02","zhangshang02");
map.put("03","zhangshang03");
Set<String> KeySet = map.keySet(); //先获取map集合的所有键的Set集合,KeySet();
Iterator<String> it = KeySet.iterator(); //有了Set集合,就可以获取其迭代器
while(it.hasNext()){
String key = it.next(); //获取每一个键
String value = map.get(key); //有了键可以通过map集合的get方法获取其对应值
System.out.println("key:"+key+",value:"+value);
}
}
}
也可以不用迭代器遍历,用增强FOR循环
HashMap<String, Integer> hm = new HashMap<>();
hm.put("张三", 23);
hm.put("李四", 24);
hm.put("王五", 25);
hm.put("赵六", 26);
for(String key :map.keySet) { //map.keySet是所有键的集合
System.out.println(key+ "=" + map.get(key));
}
Entry是Map里面的一个内部接口,将键和值封装成了Entry对象,并存储在Set集合中
HashMap类有一个叫做Entry的内部类。这个Entry类包含了key-value作为实例变量。每当往hashmap里面存放key-value对的时候,都会为它们实例化一个Entry对象,这个Entry对象就会存储在前面提到的Entry数组table中。Entry具体存在table的那个位置是根据key的hashcode()方法计算出来的hash值(来决定)。
import java.util.*;
class MapDemo
{
public static void main(String[] args)
{
Map<String , String> map = new HashMap<String , String>();
map.put("01","zhangshang01");
map.put("02","zhangshang02");
map.put("03","zhangshang03");
map.put("04","zhangshang04");
//Map.Entry说明Entry是Map的内部接口,将键和值封装成了Entry对象,并存储在Set集合中
Set<Map.Entry<String,String>> entrySet = map.entrySet(); //映射关系取出,存入到Set集合中
//获取每一个对象
Iterator<Map.Entry<String , String>> it = entrySet.iterator();
while(it.hasNext())
{
Map.Entry<String,String> me = it.next();
String key = me.getKey(); //根据键值对对象获取键
String value = me.getValue(); //根据键值对对象获取值
System.out.println(key+":"+value);
}
}
}
增强FOR循环
for(Entry<String,Integer> en : hm.entrySet()) {
System.out.println(en.getKey() + "=" + en.getValue());
}
HashMap集合键是Student值是String的案例
public class Demo5_HashMap {
/*
* * A:案例演示
* HashMap集合键是Student值是String的案例
* 键是学生对象,代表每一个学生
* 值是字符串对象,代表学生归属地
*/
public static void main(String[] args) {
HashMap<Student, String> hm = new HashMap<>();
hm.put(new Student("张三", 23), "北京");
hm.put(new Student("张三", 23), "上海");
hm.put(new Student("李四", 24), "广州");
hm.put(new Student("王五", 25), "深圳");
System.out.println(hm);
}
}
在上面代码中我们看出,键中存入了两个相同的 “张三 23” Student对象。可是HashMap中的键又不能重复,那么为什么可以这样在键中存入两个相同的Student对象呢?
因为这里的键,就相当于HashSet中的元素,其自定义对象一般要重写HashCode和equals方法,如果不重写,其New出来的对象算出来的HashCode值是不一样的。不一样就不会调用equals方法去比较,直接存入了
重写Student类中的HashCode和equals方法
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
LinkedHashMap
底层是链表实现的可以保证怎么存就怎么取
import java.util.LinkedHashMap;
public class Demo6_LinkedHashMap {
/**
* @param args
* LinkedHashMap可以保证怎么存就怎么取
*/
public static void main(String[] args) {
LinkedHashMap<String, Integer> lhm = new LinkedHashMap<>();
lhm.put("张三", 23);
lhm.put("李四", 24);
lhm.put("赵六", 26);
lhm.put("王五", 25);
System.out.println(lhm);
}
}
TreeMap
TreeMap集合键是Student值是String的案例
和TreeSet一样,当往容器中存入对象时,要重写compareTo方法,让程序知道按何种方式比较。如果没有重写就会引发ClassCastException
实现comparable接口,重写compareTo方法
public class Student implements Comparable<Student> {
private String name;
private int age;
@Override
public int compareTo(Student o) {
int num = this.age - o.age; //以年龄为主要条件
return num == 0 ? this.name.compareTo(o.name) : num;
}
}
import java.util.TreeMap;
import com.heima.bean.Student;
public class Demo7_TreeMap {
/**
* * A:案例演示
* TreeMap集合键是Student值是String的案例
*/
public static void main(String[] args) {
tm.put(new Student("张三", 23), "北京");
tm.put(new Student("李四", 13), "上海");
tm.put(new Student("赵六", 43), "深圳");
tm.put(new Student("王五", 33), "广州");
System.out.println(tm);
}
}
也可以使用比较器来比较
import java.util.Comparator;
import java.util.TreeMap;
import com.heima.bean.Student;
public class Demo7_TreeMap {
/**
* * A:案例演示
* TreeMap集合键是Student值是String的案例
*/
public static void main(String[] args) {
//demo1();
TreeMap<Student, String> tm = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num = s1.getName().compareTo(s2.getName()); //按照姓名比较
return num == 0 ? s1.getAge() - s2.getAge() : num;
}
});
tm.put(new Student("张三", 23), "北京");
tm.put(new Student("李四", 13), "上海");
tm.put(new Student("赵六", 43), "深圳");
tm.put(new Student("王五", 33), "广州");
System.out.println(tm);
}
}
练习:统计字符串中每个字符出现的次数
import java.util.HashMap;
public class Test1 {
/**
* * A:案例演示
* 需求:统计字符串中每个字符出现的次数
*
* 分析:
* 1,定义一个需要被统计字符的字符串
* 2,将字符串转换为字符数组
* 3,定义双列集合,存储字符串中字符以及字符出现的次数
* 4,遍历字符数组获取每一个字符,并将字符存储在双列集合中
* 5,存储过程中要做判断,如果集合中不包含这个键,就将该字符当作键,值为1存储,如果集合中包含这个键,就将值加1存储
* 6,打印双列集合获取字符出现的次数
*/
public static void main(String[] args) {
//1,定义一个需要被统计字符的字符串
String s = "aaaabbbbbccccccccccccc";
//2,将字符串转换为字符数组
char[] arr = s.toCharArray();
//3,定义双列集合,存储字符串中字符以及字符出现的次数
HashMap<Character, Integer> hm = new HashMap<>();
//4,遍历字符数组获取每一个字符,并将字符存储在双列集合中
for(char c: arr) {
//5,存储过程中要做判断,如果集合中不包含这个键,就将该字符当作键,值为1存储,如果集合中包含这个键,就将值加1存储
if(!hm.containsKey(c)) { //如果不包含这个键
hm.put(c, 1);
}else {
hm.put(c, hm.get(c) + 1);
}
}
//6,打印双列集合获取字符出现的次数
for (Character key : hm.keySet()) { //hm.keySet()代表所有键的集合
System.out.println(key + "=" + hm.get(key));//hm.get(key)根据键获取值
}
}
}