目录
Map<K,V>
概念
键值的映射关系的一种集合(接口),键和值是一一对应的关系,HashMap和TreeMap是Map集合的两个子实现类。
Map类的操作都是针对键操作的,跟值无关。
键是唯一的,不能包含重复的键。
区别
Map | Collection |
是一种键和值的映射关系 | 单列集合,只能存储一种类型的元素 |
HashMap | Hashtable | |
安全性 | 不安全,不同步 | 安全,同步 |
效率 | 效率高 | 效率低 |
键和值 | 允许存在null | 不允许存在null |
间接关系
HashSet依赖于Map接口的子实现类HashMap。
TreeSet依赖于Map接口的子实现类TreeMap。
常用方法
添加功能
V put(K key,V value)
将指定的值和键关联起来,如果当前的这个键是一次存储,则返回值null,如果不是第一次存储,返回值是第一次对应的值,当前的值就把之前的键对应的值替换掉。
import java.util.HashMap;
import java.util.Map;
public class MapTest {
public static void main(String[] args) {
Map<String, String> m = new HashMap<String, String>();
System.out.println(m.put("张三", "李四"));// null
System.out.println(m.put("张三", "王五"));// 李四
}
}
获取功能
Set<Map.Entry<K,V>> entrySet()
和Map集合的遍历有关系(键值对对象)
Set<K> keySet()
获取映射关系中所有的键的集合
int size()
返回此映射中的键-值映射关系数
get(Object key)
返回到指定键所映射的值
删除功能
void clear()
删除所有映射关系
remove(Object key)
如果存在一个键的映射关系,则将其从此映射中移除
判断功能
boolean containsKey(Object key)
如果此映射包含指定键的映射关系,则返回 true
boolean containsValue(Object value)
映射关系中是否包含指定的值
boolean isEmpty()
判断映射关系是否为空
Map集合的遍历
利用keySet()和get(Object key)
实际开发这种方式最常用。
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapTest {
public static void main(String[] args) {
Map<String, String> m = new HashMap<String, String>();
m.put("张三", "刘一");
m.put("李四", "陈二");
m.put("王五", "孙七");
m.put("赵六", "周八");
//keySet()获取映射关系中所有的键的集合
Set<String> s=m.keySet();
for (String key : s) {
//通过键找值
//get(Object key)返回到指定键所映射的值
String value=m.get(key);
System.out.println(key+"--\t"+value);
}
}
}
/**
李四-- 陈二
张三-- 刘一
王五-- 孙七
赵六-- 周八
*/
利用entrySet()
Set<Map.Entry<K,V>> entrySet() 获取所有的键值对对象
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapTest {
public static void main(String[] args) {
Map<String, String> m = new HashMap<String, String>();
m.put("张三", "刘一");
m.put("李四", "陈二");
m.put("王五", "孙七");
m.put("赵六", "周八");
//获取所有的键值对对象
Set<Map.Entry<String, String>> s =m.entrySet();
for (Map.Entry<String, String> e : s) {
//获取到每一个键值对对象
//通过键值对对象找键和值
String key=e.getKey();
String value=e.getValue();
System.out.println(key+"---\t"+value);
}
}
}
/**
李四--- 陈二
张三--- 刘一
王五--- 孙七
赵六--- 周八
*/
HashMap集合
基于哈希表的实现的Map接口。
存储自定义对象,因为键具有唯一性,当自定义对象作为键的时候需要在自定义对象中重写hashCode()和equals()方法才能保证唯一性。
import java.util.HashMap;
import java.util.Set;
class Student{
private String name;
private int age;
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() {
super();
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
//重写hashCode
@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;
}
//重写equals
@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;
}
@Override
public String toString() {
return "学生:[姓名=" + name + ", 年龄=" + age + "]";
}
}
public class Test {
public static void main(String[] args) {
//创建map集合对象
HashMap<Student, String> map=new HashMap<Student, String>();
//创建学生对象
Student s1=new Student("张三", 21);
Student s2=new Student("张三", 21);//姓名年龄一样算同一个人
Student s3=new Student("张三", 23);//姓名一样年龄不同算两个人
Student s4=new Student("李四", 20);
Student s5=new Student("王五", 25);
Student s6=new Student("王五", 25);
//添加到map集合中
map.put(s1, "132001");
map.put(s2, "132002");
map.put(s3, "132003");
map.put(s4, "132004");
map.put(s5, "132005");
map.put(s6, "132006");
//获取键的集合
Set<Student> k=map.keySet();
for (Student s : k) {//遍历键的集合
String v=map.get(s);//通过键找对应的值
System.out.println(s.toString()+"\t学号:"+v);
}
}
}
/**
学生:[姓名=李四, 年龄=20] 学号:132004
学生:[姓名=张三, 年龄=23] 学号:132003
学生:[姓名=张三, 年龄=21] 学号:132002
学生:[姓名=王五, 年龄=25] 学号:132006
*/
HashMap的嵌套遍历
HashMap集合的键也为HashMap集合的情况。
import java.util.HashMap;
import java.util.Set;
public class Test3 {
public static void main(String[] args) {
// 定义以和HashMap集合,值为HashMap集合
HashMap<String, HashMap<String, Integer>> gra = new HashMap<String, HashMap<String, Integer>>();
// 定义一班的学生
HashMap<String, Integer> cla1 = new HashMap<String, Integer>();
cla1.put("张三", 18);
cla1.put("李四", 21);
gra.put("一班", cla1);
// 定义二班的学生
HashMap<String, Integer> cla2 = new HashMap<String, Integer>();
cla2.put("王五", 22);
cla2.put("赵六", 24);
gra.put("二班", cla2);
// 遍历
Set<String> gks = gra.keySet();// 获取大集合gra键的集合
for (String gk : gks) {// 遍历大集合gra键的集合
System.out.println("班级:" + gk);// 输出班级信息
HashMap<String, Integer> gv = gra.get(gk);// 通过大集合的键获取值
Set<String> cks = gv.keySet();// 遍历
for (String ck : cks) {
Integer cv = gv.get(ck);
System.out.println("\t姓名:" + ck + "\t年龄:" + cv);
}
}
}
}
/**结果:
班级:一班
姓名:李四 年龄:21
姓名:张三 年龄:18
班级:二班
姓名:王五 年龄:22
姓名:赵六 年龄:24
*/
LinkedHashMap
有序的HashMap集合
上面HashMap的代码使用LinkHashMap再做一次
import java.util.LinkedHashMap;
import java.util.Set;
public class Test {
public static void main(String[] args) {
// 创建LinkedHashMap集合对象
LinkedHashMap<Student, String> map = new LinkedHashMap<Student, String>();
// 创建学生对象
Student s1 = new Student("张三", 21);
Student s2 = new Student("张三", 21);// 姓名年龄一样算同一个人
Student s3 = new Student("张三", 23);// 姓名一样年龄不同算两个人
Student s4 = new Student("李四", 20);
Student s5 = new Student("王五", 25);
Student s6 = new Student("王五", 25);
// 添加到LinkedHashMap集合中
map.put(s1, "132001");
map.put(s2, "132002");
map.put(s3, "132003");
map.put(s4, "132004");
map.put(s5, "132005");
map.put(s6, "132006");
// 获取键的集合
Set<Student> k = map.keySet();
for (Student s : k) {// 遍历键的集合
String v = map.get(s);// 通过键找对应的值
System.out.println(s.toString() + "\t学号:" + v);
}
}
}
/**
学生:[姓名=张三, 年龄=21] 学号:132002
学生:[姓名=张三, 年龄=23] 学号:132003
学生:[姓名=李四, 年龄=20] 学号:132004
学生:[姓名=王五, 年龄=25] 学号:132006
*/
可以发现,使用方法和HashMap集合一样但是保证了元素的有序性,存入和取出顺序一致
哈希表
在说到哈希表前我们需要先回顾数组和链表:
数组 | 链表 | |
空间复杂度 | 存储区间是连续的,占用内存严重,空间复杂度很大。 | 存储区间离散,占用内存比较宽松,空间复杂度很小 |
时间复杂度 | 二分查找时间复杂度小,O(1) | 时间复杂度很大,O(N) |
特点 | 查询快,增删慢 | 增删快,查询慢 |
而哈希表就是基于以上的特点诞生的,他的特点是查询和增删都相对容易。
哈希表相当于是数组+链表的形式实现的,数组中每个元素存储的是一个链表的头节点。链表按照 元素的哈希值%数组长度 得到的值为该链表存储的数组的下标。如数组长度为16,链表元素的哈希值为171,171%16=11,则该链表对应到数组中下标为11的位置。
HashMap中创建了一个Entry[ ]数组,Entry是一个匿名内部类,在其中存储了键值对关系。所以HashMap底层还是通过数组实现的。存储时通过键K,的哈希码值%Entry数组的长度来在Entry数组中存储。如果不同的k计算出相同的脚标值时,利用链表的方式将后面进来的替换到数组中,然后将前一个通过Entry中的next属性链接到后进来的元素上。
TreeMap<K,V>
基于红黑树结构的双链集合,会自然排序键,需要实现Comparator接口中的compare方法才能完成排序。
import java.util.Comparator;
import java.util.Set;
import java.util.TreeMap;
public class Test2 {
public static void main(String[] args) {
// 创建TreeSet集合对象
TreeMap<Student, String> map = new TreeMap<Student, String>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num1 = s1.getName().length() - s2.getName().length();// 按姓名长度排序
int num2 = num1 == 0 ? s1.getAge() - s2.getAge() : num1;// 长度一样按年龄排序
return num2;
}
});
// 创建学生对象
Student s1 = new Student("zhangsan", 21);
Student s2 = new Student("zhangsan", 21);// 姓名年龄一样算同一个人
Student s3 = new Student("zhangsan", 23);// 姓名一样年龄不同算两个人
Student s4 = new Student("lisis", 20);
Student s5 = new Student("wangwu", 25);
Student s6 = new Student("zhaoliu", 24);
Student s7 = new Student("lisis", 22);
// 添加到LinkedHashMap集合中
map.put(s1, "132001");
map.put(s2, "132002");
map.put(s3, "132003");
map.put(s4, "132004");
map.put(s5, "132005");
map.put(s6, "132006");
map.put(s7, "132007");
// 获取键的集合
Set<Student> k = map.keySet();
for (Student s : k) {// 遍历键的集合
String v = map.get(s);// 通过键找对应的值
System.out.println(s.toString() + "\t学号:" + v);
}
}
}
/**
姓名:lisis 年龄:20 学号:132004
姓名:lisis 年龄:22 学号:132007
姓名:wangwu 年龄:25 学号:132005
姓名:zhaoliu 年龄:24 学号:132006
姓名:zhangsan 年龄:21 学号:132002
姓名:zhangsan 年龄:23 学号:132003
*/
Hashtable<K,V>
线程安全的双链集合,使用方式和HashMap集合类似。
需要注意Hashtable集合不允许键值对中存在null的元素,如果存在null会引发NullPointerException异常。
Collections
集合的工具类,区别于Collection是集合的根接口。
常用方法
sort(List<T> list)
升序排序集合,如果集合中的元素是自定义对象需要实现Comparable接口中的compareTo方法,更多情况下使用Comparator匿名内部类的方式,需要重写compare方法。
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Test5 {
public static void main(String[] args) {
ArrayList<Student> list=new ArrayList<Student>();
//使用的学生类还是上文之前代码中定义的学生类
Student s1=new Student("张三", 21);
Student s2=new Student("李四", 20);
Student s3=new Student("王五", 25);
Student s4=new Student("木婉清", 21);
Student s5=new Student("李清浅", 20);
Student s6=new Student("言子矜", 20);
Student s7=new Student("周糖芯", 25);
list.add(s1);
list.add(s2);
list.add(s3);
list.add(s4);
list.add(s5);
list.add(s6);
list.add(s7);
Collections.sort(list, new Comparator<Student>() {//需要重写Comparator接口中的compare方法
@Override
public int compare(Student s1, Student s2) {
int num1=s1.getAge()-s2.getAge();//按年龄排序
int num2=num1==0?s1.getName().length()-s2.getName().length():num1;//年龄一样按姓名长度排序
return num2;
}
} );
//遍历
for (Student stu : list) {
System.out.println(stu.toString());
}
}
}
/**
结果为:
姓名:李四 年龄:20
姓名:李清浅 年龄:20
姓名:言子矜 年龄:20
姓名:张三 年龄:21
姓名:木婉清 年龄:21
姓名:王五 年龄:25
姓名:周糖芯 年龄:25
*/
binarySearch(List<T> list, T key)
集合的二分法查找,前提是集合元素有序
max(Collection coll)
获取集合中的最大值
reverse(List<?> list)
将集合中的元素顺序反转
shuffle(List<?> list)
将集合中的元素打乱
import java.util.ArrayList;
import java.util.Collections;
public class Test4 {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList<Integer>();
list.add(10);
list.add(55);
list.add(15);
list.add(45);
list.add(12);
list.add(34);
list.add(24);
list.add(33);
System.out.println(list);//[10, 55, 15, 45, 12, 34, 24, 33]
Collections.sort(list);//升序排列
System.out.println(list);//[10, 12, 15, 24, 33, 34, 45, 55]
int i=Collections.binarySearch(list, 24);//二分法查找
System.out.println(i);//3
int max=Collections.max(list);//获取最大值
System.out.println(max);//55
Collections.reverse(list);//反转
System.out.println(list);//[55, 45, 34, 33, 24, 15, 12, 10]
Collections.shuffle(list);//随机打乱
System.out.println(list);//[34, 33, 55, 24, 45, 10, 12, 15]
}
}
同步相关方法
synchronizedList(List<T> list)
静态方法,返回一个加同步锁的List集合。可以将线程不安全的List集合变成线程安全的
synchronizedMap(Map<K,V> m)
静态方法,返回一个加同步锁的Map集合。可以将线程不安全的Map集合变成线程安全的
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Test2 {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<String>());
Map<String, Integer> map = Collections.synchronizedMap(new HashMap<String,Integer>());
}
}
运用实例
模拟斗地主的洗牌和发牌
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.TreeSet;
public class Test6 {
public static void main(String[] args) {
// 定义花色
String[] col = { "♣", "♠", "♦", "♥" };
// 定义点数
String[] num = { "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K" };
// 定义扑克牌集合
HashMap<Integer, String> pk = new HashMap<Integer, String>();
// 定义扑克牌编号集合
ArrayList<Integer> arr = new ArrayList<Integer>();
// 定义计数变量,arr集合存储编号,pk集合存储编号和对应的扑克牌
int i = 0;
for (String n : num) {
for (String c : col) {
arr.add(i);
pk.put(i, c + n);
i++;
}
}
// 装入大小王
arr.add(i);
pk.put(i, "小王");
i++;
arr.add(i);
pk.put(i, "大王");
// 洗牌
Collections.shuffle(arr);
// 定义玩家和底牌
TreeSet<Integer> play1 = new TreeSet<Integer>();
TreeSet<Integer> play2 = new TreeSet<Integer>();
TreeSet<Integer> play3 = new TreeSet<Integer>();
TreeSet<Integer> dipai = new TreeSet<Integer>();
// 发牌
for (int j = 0; j < arr.size(); j++) {
if (j >= arr.size() - 3) {
dipai.add(arr.get(j));
} else if (j % 3 == 0) {
play1.add(arr.get(j));
} else if (j % 3 == 1) {
play2.add(arr.get(j));
} else if (j % 3 == 2) {
play3.add(arr.get(j));
}
}
//看牌
//定义一个看牌的方法dela
dela("玩家1", play1, pk);
dela("玩家2", play2, pk);
dela("玩家3", play3, pk);
dela("底牌", dipai, pk);
}
/**
* 查看玩家牌的方法
* @param play 玩家姓名
* @param arr 玩家牌的编号
* @param pk 总的扑克集合
*/
public static void dela(String play,TreeSet<Integer> arr,HashMap<Integer, String> pk) {
//输出玩家信息
System.out.print(play+":");
//遍历玩家编号
for (Integer num : arr) {
//通过编号寻找对应的牌
System.out.print(pk.get(num)+" ");
}
System.out.println();
}
}
/**
玩家1:♠A ♣2 ♦2 ♥2 ♥3 ♣4 ♦4 ♣6 ♣8 ♦8 ♣10 ♥10 ♦Q ♣K ♦K ♥K 小王
玩家2:♦A ♠2 ♠3 ♦3 ♠4 ♣5 ♠5 ♠7 ♥7 ♠8 ♠9 ♦9 ♣J ♦J ♥J ♠Q 大王
玩家3:♣A ♥A ♣3 ♥4 ♦5 ♥5 ♠6 ♦6 ♥6 ♣7 ♦7 ♥8 ♥9 ♦10 ♠J ♣Q ♥Q
底牌:♣9 ♠10 ♠K
*/