Map
- 集是一个集合,它可以快速的查找现有的元素。但是,要查看一个元素,需要有要查找元素的副本。这不是一种非常通用的查找方式。通常,我们知道某些键的信息,并想要查找与之对应的元素。映射数据结构就是为此设计的。映射用来存放
键/值对集
。提供了键,就能找到值,键与值一一对应。例如,有一张关于员工信息的记录表,键为员工ID,值为Employee对象。 - Java为映射提供了Map接口;
映射顺序 定义为迭代器在映射的 collection 视图上返回其元素的顺序
。某些映射实现可明确保证其顺序,如 TreeMap 类;另一些映射实现则不保证顺序,如 HashMap 类。- Map接口中的内部类:
public static interface Map.Entry<K,V>映射项(键-值对):
Map.entrySet 方法返回映射的 collection 视图,我个人理解就是该方法返回的是的对象能查看每个结点对象的键与值;后面在遇到集合遍历的时候可以很好的理解。
- Map接口中的方法:
方法见名知意:
- Map接口的常见的实现类:
HashMap、TreeMap、Hashtable、LinkedHashMap
HashMap类
- HashMap概述
1、 在JDK1.8之前,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。
2、 而JDK1.8中,HashMap采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
【JDK1.8之前的HashMap结构】
【JDK1.8之后的HashMap结构】
3、上图很形象的展示了HashMap的数据结构(数组+链表+红黑树),桶中的结构可能是链表,也可能是红黑树,红黑树的引入是为了提高效率。
- HashMap的方法:
- 代码演示:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MyTest1
{
public static void main(String[] args)
{
HashMap<String, String> map = new HashMap<>();
String put = map.put("s000", "张三");
System.out.println(put);//null
String put1 = map.put("s000", "张三1");
System.out.println(put1);//张三1
//得出结论:键值对集合当中,键相同,值覆盖
map.put("s001","田七");
map.put("s002","李四");
map.put("s003","王五");
map.put("s004","赵六");
//根据键删除删除键值对,返回的是键对应的值
String s = map.remove("s001");
System.out.println(s);//田七
//判断集合是否为空
boolean b = map.isEmpty();
System.out.println(b);//false
//获取集合长度
System.out.println(map.size());//4
//与判断相关的方法
//判断有没有相关的键
System.out.println(map.containsKey("s001"));//false
//判断有没有相关的值
System.out.println(map.containsValue("李四"));//true
//键找值,键会映射到值
String s2 = map.get("s002");
System.out.println(s2);//李四
//如何去遍历集合——键找值
//1、获取所有键的集合
Set<String> keySet = map.keySet();
for (String str : keySet)
{
System.out.println(str+"---"+map.get(str));
}
//2、遍历方式2
//获取出键值对 对象 Node
Set<Map.Entry<String, String>> entries = map.entrySet();
//上面的写法是 外部类.内部类,你点进去看一下,他们两个都是接口,点到它们的实现类hashMap里面
//遍历键值对集合,通过键值对对象里面的方法获取键和值
for (Map.Entry<String, String> entry : entries)
{
System.out.println(entry.getKey()+"-----"+entry.getValue());
}
/*s004-----赵六
s003-----王五
s000-----张三1
s002-----李四*/
//全部清空集合中的元素
/*map.clear();
System.out.println(map);*/
}
}
- Map类的集合只与键有关,我们可以通过添加元素的方法查看底层的代码;它的底层是调用键对象的equals()和hashCode()方法来保证键的唯一性;与hashSet集合一样,重写hashCode()是为了减少碰撞。
Hashtable类
- Hashtable类与HashMap的区别有两点:
1、前者允许null键与null值,后者不允许存在null元素
2、前者是线程不安全,效率高;后者是线程安全,效率低;在实际开发中,我们选用适合自己的集合使用。 - 除以上不同之外,后者的方法都与前者大致一样
- 代码实现如下:
import java.util.*;
public class MyTest2
{
//基于哈希表的 Map 接口的实现。
// 此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。
// (除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)
// 此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
//hashMap:无序(索引算出来在哪就在哪)且唯一(哈希表保证的)
//键的数据结构式哈希表,键唯一(键重写了equals(),重写hashCode()是为了均匀分布),无序
//hashMap是允许出现null键和null值,线程不安全效率高
public static void main(String[] args)
{
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put(null,100);
hashMap.put("abc",null);
hashMap.put(null,null);
System.out.println(hashMap);
System.out.println(hashMap.get(null));
//键找值,键相同,值覆盖 null
Set<String> keySet = hashMap.keySet();
for (String s : keySet)
{
System.out.println(s + "-----" + hashMap.get(s));
}
/*null-----null
abc-----null*/
Set<Map.Entry<String, Integer>> entries = hashMap.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey() + "---" + entry.getValue());
/*null---null
abc---null*/
}
//hashMap和hashTable的区别:
//前者是不允许存null键和null值
//线程安全,效率低
Hashtable<String, String> hashtable = new Hashtable<>();
//hashtable.put(null,null);
//NullPointerException
//其他方法都一样
//获取所有的值的集合
Collection<Integer> values = hashMap.values();
System.out.println(values);
//[null, null]
}
}
LinkedHashMap类
- 看到集合的名字就知道,这个集合元素唯一且有序;哈希结构保证了唯一性,链表结构保证了有序;
- 该集合与其它集合比较:
- Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。
- 此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。
- 此实现可以让客户避免未指定的、由 HashMap(及 Hashtable)所提供的通常为杂乱无章的排序工作,同时无需增加与 TreeMap 相关的成本。使用它可以生成一个与原来顺序相同的映射副本,而与原映射的实现无关:
void foo(Map m) { Map copy = new LinkedHashMap(m); … }
- 注意:该集合也是线程不安全,效率低。
- LinkedHashMap类的方法:
- 代码实现:
import java.util.LinkedHashMap;
import java.util.TreeMap;
public class LinkedHashMap集合
{
public static void main(String[] args)
{
LinkedHashMap<Object, Object> linkedHashMap = new LinkedHashMap<>();
//听名字就知道是元素唯一且有序
//链表保证了键有序,哈希表保证了键唯一
linkedHashMap.put(10,"abc");
linkedHashMap.put(23,"def");
linkedHashMap.put(13,"ghi");
linkedHashMap.put(104,"jkl");
linkedHashMap.put(87,"mno");
linkedHashMap.put(56,"pqr");
linkedHashMap.put(7,"stu");
linkedHashMap.put(38,"vwx");
linkedHashMap.put(24,"yz");
System.out.println(linkedHashMap);
//{10=abc, 23=def, 13=ghi, 104=jkl, 87=mno, 56=pqr, 7=stu, 38=vwx, 24=yz}
}
}
TreeMap类
- TreeMap与TreeSet一样,后者底层是用前者存的,treeMap键的数据结构是二叉树,键唯一,且可以对键进行排序。
- 排序的两种方式:
1、自然排序:你在创建集合对象的时候采用的是空参构造就默认采用了自然排序,这时候你的键所在的类必须实现接口Comparable,并重写里面的compareTo方法;
2、比较器排序:你在创建集合对象的时候传递一个比较器Comparator进去,并在你的比较器里面重写compare方法,至于里面的代码逻辑取决于你想怎么排序集合; - TreeMap类也是线程不安全,效率高;
- TreeMap里面的方法:
Map.Entry<K,V> ceilingEntry(K key) : 返回一个键-值映射关系,它与大于等于给定键的最小键关联;如果不存在这样的键,则返回 null
void clear() : 从此映射中移除所有映射关系
Object clone() : 返回此 TreeMap 实例的浅表副本
Comparator<? super K> comparator() : 返回对此映射中的键进行排序的比较器;如果此映射使用键的自然顺序,则返回 null
boolean containsKey(Object key) : 如果此映射包含指定键的映射关系,则返回 true
boolean containsValue(Object value) : 如果此映射为指定值映射一个或多个键,则返回 true
Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的映射关系的 Set 视图。
V get(Object key) : 返回指定键所映射的值,如果对于该键而言,此映射不包含任何映射关系,则返回 null
V put(K key, V value) : 将指定值与此映射中的指定键进行关联
void putAll(Map<? extends K,? extends V> map) : 将指定映射中的所有映射关系复制到此映射中
V remove(Object key) : 如果此 TreeMap 中存在该键的映射关系,则将其删除
int size() : 返回此映射中的键-值映射关系数
- 代码实现:
自然排序:
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class TreeMap集合
{
public static void main(String[] args)
{
//自然排序
//map集合的数据结构只与键有关
TreeMap<Student, Integer> treeMap = new TreeMap<>();
treeMap.put(new Student("张三",1),1);
treeMap.put(new Student("张三",12),12);
treeMap.put(new Student("张三",123),123);
treeMap.put(new Student("张三",14),14);
treeMap.put(new Student("张三",125),125);
treeMap.put(new Student("张三",126),126);
//获取结点对象
Set<Map.Entry<Student, Integer>> entries = treeMap.entrySet();
for (Map.Entry<Student, Integer> entry : entries) {
System.out.println(entry.getKey() + "---" + entry.getValue());
}
/*//获取键集合遍历
Set<Student> keySet = treeMap.keySet();
for (Student key : keySet)
{
System.out.println(key + "------" + treeMap.get(key));
}*/
}
}
Student类:
import java.util.Objects;
public class Student implements Comparable<Student>
{
private String name;
private int age;
private Student o;
/**
*这是一个比较的方法,
* 如果将值写死为0,
* 会出现一种现象就是只能放进去一个元素
* 因为你后面的元素与根节点元素一比,全都是一样的,返回值为0,因此就不会存进去
* 如果你返回的是1或-1,全都放在一个方向了,成了数组了,取出来的时候元素顺序正好相反
* @param
* @return
*/
@Override
public int compareTo(Student o)
{
//根据学生的年龄大小来排序
//年龄一样,并不能说明是同一对象,我们还得比较姓名是否一样
/*int num=this.age-o.age;
int num2=num==0?this.name.compareTo(o.name):num;
return num2;
//如果我们的需求是按照姓名的长短来排序,怎么写?
int ageNum=this.age-o.age;
int nameNum=this.name.length()-o.name.length();
int num=this.name.compareTo(o.name);
if(nameNum==0)
{//名字长短一样
if(ageNum==0&&num!=0)
{//年龄一样但是名字不一样
return 1;
}else if(ageNum==0&&num==0)
{//年龄一样名字也一样
return 0;
}else
{//年龄不一样
return ageNum;
}
}else
{//名字长短不一样
return nameNum;
}*/
//需求是按照姓名的长短来排序:
int i = this.name.length() - o.name.length();
//如果长度一样,还得比较姓名内容
int i1=i==0?this.name.compareTo(o.name):i;
//当长度一样,内容一样,还得比较年龄
int i2=i1==0?this.age-o.age:i1;
return i2;
//里面的比较逻辑需要你自己写,它只要一个返回值正负0
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o)
{
System.out.println(o+"-----"+this);
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode()
{
//return 0;
return Objects.hash(name, age);
//很明显,你自己去看代码,执行出来的结果碰撞次数很少
//老师的代码:
/*
* zhangsan 23
* lisi 24
* wangwu 19 + 20*11
* zhaoliu 20 + 19*11
* */
//return this.name.hashCode()+this.age*11;
//这样一来,只有完全一样才会去调用equals()
//这样就使得均匀分布
}
}
比较器排序:
import java.util.Comparator;
import java.util.Set;
import java.util.TreeMap;
public class TreeMap集合2
{
public static void main(String[] args)
{
//比较器排序
TreeMap<Student, Integer> treeMap = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int num = o1.getAge() - o2.getAge();
int num1=(num==0)?o1.getName().compareTo(o2.getName()):num;
return num1;
}
});
treeMap.put(new Student("张三",1),1);
treeMap.put(new Student("张三",12),12);
treeMap.put(new Student("张三",123),123);
treeMap.put(new Student("张三",14),14);
treeMap.put(new Student("张三",125),125);
treeMap.put(new Student("张三",126),126);
Set<Student> keySet = treeMap.keySet();
for (Student key : keySet) {
System.out.println(key + "---" + treeMap.get(key));
}
/*Student{name='张三', age=1}---1
Student{name='张三', age=12}---12
Student{name='张三', age=14}---14
Student{name='张三', age=123}---123
Student{name='张三', age=125}---125
Student{name='张三', age=126}---126*/
}
}
集合的嵌套练习
1、统计字符串中每个字符出现的次数:“aababcabcdabcde”,获取字符串中每一个字母出现的次数要求结果:
a(5)b(4)c(3)d(2)e(1)
import java.util.*;
public class MyTest1
{
public static void main(String[] args)
{
//考虑是用键值对集合来存储,键代表abcde,值代表他们出现的次数
String str="aababcabcdabcde";
HashMap<Character, Integer> hashMap = new HashMap<>();
//难点在于怎么将键值对存进去
for (int i = 0; i < str.length(); i++)
{
//截取字符作为键存到集合中
char ch = str.charAt(i);
if(!hashMap.containsKey(ch))
{
hashMap.put(ch,1);
}else{
Integer integer = hashMap.get(ch);//通过键找值
integer++;
hashMap.put(ch,integer);//键相同,值覆盖
}
}
StringBuilder sb = new StringBuilder();
Set<Map.Entry<Character, Integer>> entries = hashMap.entrySet();
for (Map.Entry<Character, Integer> entry : entries) {
sb.append(entry.getKey() + "(" + entry.getValue() + ")");
}
System.out.println(sb);
}
}
2、案例:集合嵌套之HashMap嵌套HashMap,输出结果为:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MyTest2
{
public static void main(String[] args)
{
//集合嵌套
HashMap<String, Integer> jcMap = new HashMap<>();
jcMap.put("张三",20);
jcMap.put("李四",22);
HashMap<String, Integer> jyMap = new HashMap<>();
jyMap.put("王五",21);
jyMap.put("赵六",23);
HashMap<String, HashMap<String, Integer>> maxMap = new HashMap<>();
maxMap.put("基础班",jcMap);
maxMap.put("就业班",jyMap);
//遍历集合
Set<Map.Entry<String, HashMap<String, Integer>>> entrySet = maxMap.entrySet();
for (Map.Entry<String, HashMap<String, Integer>> en : entrySet)
{
System.out.println(en.getKey()+":");
Set<Map.Entry<String, Integer>> ee = en.getValue().entrySet();
for (Map.Entry<String, Integer> e : ee)
{
System.out.println("\t"+e.getKey()+"\t"+e.getValue());
}
}
}
}
3、集合嵌套之HashMap嵌套ArrayList,假设HashMap集合的元素是ArrayList,有3个,每一个ArrayList集合的值是字符串,输出结果为:
import java.util.*;
public class MyTest3
{
public static void main(String[] args)
{
//hashMap嵌套list集合
ArrayList<String> sgList = new ArrayList<>();
sgList.add("吕布");
sgList.add("周瑜");
ArrayList<String> xaList = new ArrayList<>();
xaList.add("令狐冲");
xaList.add("林平之");
ArrayList<String> sdList = new ArrayList<>();
sdList.add("郭靖");
sdList.add("杨过");
HashMap<String, ArrayList<String>> listHashMap = new HashMap<>();
listHashMap.put("三国演义",sgList);
listHashMap.put("笑傲江湖",xaList);
listHashMap.put("神雕侠侣",sdList);
Set<Map.Entry<String, ArrayList<String>>> entrySet = listHashMap.entrySet();
for (Map.Entry<String, ArrayList<String>> entry : entrySet)
{
System.out.println(entry.getKey()+":");
Iterator<String> iterator = entry.getValue().iterator();
while (iterator.hasNext())
{
System.out.println("\t"+iterator.next());
}
}
}
}
4、集合嵌套之ArrayList嵌套HashMap,假设ArrayList集合的元素是HashMap,有3个,每一个HashMap集合的键和值都是字符串,输出结果为:
周瑜—小乔
吕布—貂蝉
郭靖—黄蓉
杨过—小龙女
令狐冲—任盈盈
林平之—岳灵珊
import java.util.*;
public class MyTest4
{
public static void main(String[] args)
{
HashMap<String, String> sgMap = new HashMap<>();
sgMap.put("周瑜","小乔");
sgMap.put("吕布","貂蝉");
HashMap<String, String> sdMap = new HashMap<>();
sdMap.put("郭靖","黄蓉");
sdMap.put("杨过","小龙女");
HashMap<String, String> xaMap = new HashMap<>();
xaMap.put("令狐冲","任盈盈");
xaMap.put("林平之","岳灵珊");
ArrayList<HashMap<String, String>> list = new ArrayList<>();
list.add(sgMap);
list.add(sdMap);
list.add(xaMap);
Iterator<HashMap<String, String>> iterator = list.iterator();
while (iterator.hasNext())
{
Set<Map.Entry<String, String>> entrySet = iterator.next().entrySet();
for (Map.Entry<String, String> entry : entrySet)
{
System.out.println(entry.getKey() + "---" + entry.getValue());
}
}
}
}
5、用集合模拟斗地主发牌,看牌
import javax.swing.*;
import java.util.*;
public class MyTest1
{
public static void main(String[] args)
{
HashMap<Integer, String> hashMap = new HashMap<>();
//hashMap里面键是【0-53】,值是花色+数字
String[] colors = {"♥", "♠", "♦", "♣"};
String[] nums = {"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"};
int i=0;
for (String num : nums)
{
for (String color : colors)
{
hashMap.put(i,color.concat(num));
i++;
}
}
//手动添加大小王
hashMap.put(i,String.valueOf("小王").concat("🌙"));
hashMap.put(i+1,String.valueOf("大王").concat("☀"));
/*System.out.println(hashMap.size());
System.out.println(hashMap);*/
//arrayList中存储的是[0-53]
ArrayList<Integer> arrayList = new ArrayList<>();
Set<Integer> keySet = hashMap.keySet();
for (Integer key : keySet)
{
arrayList.add(key);
}
//三次打乱顺序
Collections.shuffle(arrayList);
Collections.shuffle(arrayList);
Collections.shuffle(arrayList);
//System.out.println(arrayList);
//三人的牌用索引代替,用treeSet数据结构,元素自动排序
TreeSet<Integer> 高进 = new TreeSet<>();
TreeSet<Integer> 星仔 = new TreeSet<>();
TreeSet<Integer> 刀仔 = new TreeSet<>();
TreeSet<Integer> 底牌 = new TreeSet<>();
int j = 0;
for (; j < arrayList.size() - 3; j = j + 3)
{
高进.add(arrayList.get(j));
星仔.add(arrayList.get(j + 1));
刀仔.add(arrayList.get(j + 2));
}
底牌.add(arrayList.get(j));
底牌.add(arrayList.get(j + 1));
底牌.add(arrayList.get(j + 2));
lookPoker("高进",hashMap,高进);
lookPoker("星仔",hashMap,星仔);
lookPoker("刀仔",hashMap,刀仔);
lookPoker("底牌",hashMap,底牌);
}
private static void lookPoker(String name, HashMap<Integer, String> poker, TreeSet<Integer> ownPoker)
{
StringBuilder sb = new StringBuilder();
System.out.println(name+"拿的牌是:");
Iterator<Integer> iterator = ownPoker.iterator();
while(iterator.hasNext())
{
Integer next = iterator.next();
String s = poker.get(next);
sb=sb.append(s).append("\t");
}
System.out.println(sb);
System.out.println();
}
}
/*输出结果为:
高进拿的牌是:
♥A ♠2 ♣2 ♥3 ♥4 ♠4 ♦4 ♥5 ♥8 ♦8 ♣9 ♣Q ♠K ♦K ♣K 小王🌙 大王☀
星仔拿的牌是:
♠3 ♣3 ♠5 ♦5 ♣5 ♦6 ♠7 ♠8 ♦9 ♥10 ♠10 ♠J ♦J ♣J ♥Q ♠Q ♦Q
刀仔拿的牌是:
♦A ♣A ♥2 ♦2 ♦3 ♣4 ♥6 ♠6 ♥7 ♦7 ♣7 ♣8 ♥9 ♠9 ♣10 ♥J ♥K
底牌拿的牌是:
♠A ♣6 ♦10 */
Collections工具类
-
Collections类的描述:
-
Collections 中的方法:
-
public static void sort(List list): 排序,默认按照自然顺序
-
public static int binarySearch(List<?> list,T key): 二分查找
-
public static T max(Collection<?> coll): 获取最大值
-
public static void reverse(List<?> list): 反转
-
public static void shuffle(List<?> list): 随机置换
-
代码实现:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class MyTest1
{
public static void main(String[] args)
{
//根据你的需求,选取容器
//Collections,它是一个工具类,针对的是collection集合,
//提供了一些方便的方法
Collections.shuffle(new ArrayList<>());//随机打乱你集合中的元素的顺序
ArrayList<Integer> list = new ArrayList<>();
list.add(12);
list.add(23);
list.add(34);
list.add(13);
Collections.sort(list);
System.out.println(list);
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
Collections.shuffle(new ArrayList<>());//随机打乱集合中元素的顺序
//二分查找
int i = Collections.binarySearch(list, 12);
System.out.println(i);
//反转集合元素
Collections.reverse(list);
System.out.println(list);
//获取集合中的最大最小值
Integer max = Collections.max(list);
System.out.println(max);
}
}