-
Map是由一系列键值对组成的集合,提供了key到Value的映射。同时它也没有继承 Collection。
-
Map是一个key对应一个value,所以它不能存在相同的 key 值,当然value值可以相同
-
Map接口提供了重要的针对键、值进行操作的接口方法
1.1HashMap
特点:
-
1.底层实现1.7之前:数组+链表 1.8以后:数组+链表+红黑树
-
2.key不允许重复,如果key的值相同,后添加的数据会覆盖之前的数据
-
3.HashMap是非线程安全的,允许存放null键,null值。
案例:
package Map;
import set.Teacher;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class HashMapTest {
public static void main(String[] args) {
//创建Hashmap对象
Map<String, Teacher> maps=new HashMap<>();
//创建teacher对象
Teacher t1=new Teacher("唐僧",25,100);
Teacher t2=new Teacher("孙悟空",3000,80);
Teacher t3=new Teacher("猪八戒",2000,60);
Teacher t4=new Teacher("沙悟净",2000,50);
Teacher t5=new Teacher("沙悟净1",2000,50);
//Teacher对象添加到HashMap中
maps.put("唐僧",t1);
// maps.put("孙悟空",t1);
maps.put("孙悟空",t2);
maps.put("猪八戒",t3);
maps.put("沙悟净",t4);
maps.put("沙悟净",t5);
System.out.println("maps的数量为:"+maps.size());
System.out.println("maps的containsKey:"+maps.containsKey("孙悟空"));
System.out.println("maps的containsValue:"+maps.containsValue(t2));
Teacher t0=maps.get("猪八戒");
System.out.println("maps的get方法,根据key获取value的值"+t0);
System.out.println("-----------遍历key--------------");
Set<String> keys=maps.keySet();
for (String key:keys){
System.out.println(key);
}//遍历为4是因为t4和t5的key值相等,当t5要存时,t4早它一步存储,就不存储t5的key
System.out.println("===========遍历value----------");
Collection<Teacher> values=maps.values();
for (Teacher value:values){
System.out.println(value);
}//遍历是沙悟净1,因为t5和t4的key一样,put时t5在t4后,改变了value值
System.out.println("根据key删除元素");
Teacher tt=maps.remove("猪八戒");
System.out.println("删除的元素为:"+tt);
System.out.println("===========keyvalue对对象Entry----------");
//用getKey(),getValue()
Set<Map.Entry<String,Teacher>> entries=maps.entrySet();
for (Map.Entry<String,Teacher>entry:entries){
String key=entry.getKey();
Teacher value=entry.getValue();
System.out.println("key为:"+key+",value为:"+value);
}
}
}
package set;
public class Teacher implements Comparable<Teacher>{
private String name;
private int age;
private int level;
public Teacher(String name, int age, int level) {
this.name = name;
this.age = age;
this.level = level;
}
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 int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
//比较两个对象大小(通过年龄比大小)
//大于0 当前对象大于传入的对象o,小于0 当前对象小于传入的对象o
//等于0表示相等
@Override
public int compareTo(Teacher o) {
return this.age-o.age;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
", level=" + level +
'}';
}
}
maps的数量为:4
maps的containsKey:true
maps的containsValue:true
maps的get方法,根据key获取value的值Teacher{name='猪八戒', age=2000, level=60}
-----------遍历key--------------
孙悟空
沙悟净
猪八戒
唐僧
===========遍历value----------
Teacher{name='孙悟空', age=3000, level=80}
Teacher{name='沙悟净1', age=2000, level=50}
Teacher{name='猪八戒', age=2000, level=60}
Teacher{name='唐僧', age=25, level=100}
根据key删除元素
删除的元素为:Teacher{name='猪八戒', age=2000, level=60}
===========keyvalue对对象Entry----------
key为:孙悟空,value为:Teacher{name='孙悟空', age=3000, level=80}
key为:沙悟净,value为:Teacher{name='沙悟净1', age=2000, level=50}
key为:唐僧,value为:Teacher{name='唐僧', age=25, level=100}
package Map;
import set.Student;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class HashMapTest2 {
public static void main(String[] args) {
Map<Student,Integer> maps= new HashMap<>();
//Students类重写hashcode和equals,所有hashmap判断key是否相等,根据Student的hashcode和equals
//创建Student对象
Student s1=new Student("001","jack",20);
Student s2=new Student("002","jim",22);
Student s3=new Student("003","luck",23);
Student s4=new Student("001","laowang",28);
maps.put(s1,100);
maps.put(s2,120);
maps.put(s3,80);
maps.put(s4,500);
System.out.println("maps的数量是:"+maps.size());
for (Map.Entry<Student,Integer> entry:maps.entrySet()){
System.out.println(entry);
}
System.out.println("=================");
//HashSet是重写的HashMap key来判断不重复 value不包含
Set<Student> sets=new HashSet<>();
sets.add(s1);
sets.add(s2);
sets.add(s3);
sets.add(s4);
System.out.println("sets的数量是:"+sets.size());
for (Student s:sets){
System.out.println(s);
}
}
}
package set;
public class Student {
private String no;
private String name;
private int age;
public Student(String no, String name, int age) {
this.no = no;
this.name = name;
this.age = age;
}
public String getNo() {
return no;
}
// public void setNo(String no) {
// this.no = no;
// }
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;
}
@Override
public String toString() {
return "Student{" +
"no='" + no + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
//如果两个学生那个学号相同认定同一个学生
//使用equals判断对象是否相等
@Override
public boolean equals(Object obj) {
Student otherStu=(Student) obj;
if (this.getNo()==null||otherStu.getNo()==null){
return false;
}
return this.getNo().equals((otherStu.getNo()));
}
//重写hashCode 适用hashset和hashmap
//equals相等hashcode必须相等,hashcode相等equals不一定相等
@Override
public int hashCode() {
//只有相同的学号字符串相等,hashcode也相同
//不同的字符串,hashcode有可能相同,不影响,因为hashset和hashmap
//盘算hash相等之后判断equals
return this.getNo().hashCode();
}
}
数据结构:
java1.7 HashMap结构
大方向上,HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。上图中,每个绿色
的实体是嵌套类 Entry 的实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next
-
capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。
-
loadFactor:负载因子,默认为 0.75。当前容器满时,当前容量*0.75
-
threshold:扩容的阈值,等于 capacity * loadFactor
java1.8 HashMap结构:
Java1.8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑 树 组成。
根据 Java1.7 HashMap 的介绍,查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。为了降低这部分的开销,在 Java1.8 中,当链表中的元素超过了 8 个以后, 会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。
-
如何判断key是否相同:第一步计算key的hashcode是否想相同,如果不同,就认为key不相同,如果相同进行第二步判断,判断key的equals是否为true,如果为false就是认为key不相同,如果为true就认为key相同
-
所以我们重写hashcode和equals的原则,hashcode相同,equals不一定相等,但是equals相等,hashcode必须相同
重点:hashmap()中数据存放位置和hashcode()相关,当存储数据时,它会通过底层的hashcode(),得出它的哈希值,hash值对当前长度取余,余数就是数据存放在hashmap中的位置
1.2HashTable
-
Hashtable,它的操作接口和HashMap相同。
-
HashMap和HashTable的区别在于:
-
Hashtable是线程安全的,而HashMap是非线程安全的
-
Hashtable不允许空的键值对,而HashMap可以
-
Hashtable与HashMap都实现Map接口,但二个类的继承的父类不是同一个
-
HashTable底层实现:数组+链表+红黑树
案例:
package Map;
import com.sun.javafx.collections.MappingChange;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.stream.Stream;
public class HashTableTest {
public static void main(String[] args) {
Hashtable<String,Integer> hashtable=new Hashtable<>();
// hashtable.put(null,1);//运行报错,key不能是null,空指针
// hashtable.put("a",null);运行报错,value不能是null,空指针
HashMap<String,Integer> hashMap=new HashMap<>();
hashMap.put(null,1);//key可以为null
hashMap.put("jack",null);//value可以为null
hashMap.put(null,null);
//hashmap的key和value都能为null,因为key不能重复,无论存储key为null多少次,最终就有一个key为null,value会被覆盖
for (Map.Entry<String,Integer>entry:hashMap.entrySet()){
System.out.println(entry);
}
}
}
1.3ConcurrentHashMap
-
特点:ConcurrentHashMap是线程安全并且高效的HashMap,比HashTable效率高的多
key和value都不能为null
-
常用方法:同HashMap
-
数据结构:JDK8 数组+链表+红黑树,数组的结构可能是链表,也可能是红黑树,红黑树是为了提高查找效率。采用CAS+Synchronized保证线程安全。CAS表示原子操作,例如:i++不是原子操作。Synchronized:表示锁,多线程能够保证只有一个线程操作。
ConcurrentHashMap比HashTable效率要高
案例:
package Map;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapTest {
public static void main(String[] args) {
// ConcurrentHashMap 的value和key都不能为null
ConcurrentHashMap<String,Integer> concurrentHashMap=new ConcurrentHashMap<>();
concurrentHashMap.put("jack",20);
System.out.println("concurrentHashMap的数量为:"+concurrentHashMap.size());
Set<Map.Entry<String,Integer>>entries=concurrentHashMap.entrySet();
for (Map.Entry<String,Integer>entry:entries){
System.out.println(entries);
}
}
}
1.4LinkedHashMap
-
LinkedHashMap继承自HashMap,它主要是用链表实现来扩展HashMap类,HashMap中条目是没有顺序的,但是在LinkedHashMap中元素既可以按照它们插入的顺序排序,也可以按它们最后一次被访问的顺序排序
-
保持Map中元素的插入顺序或者访问顺序,就使用LinkedHashMap
案例 :
package Map;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Set;
public class LinkedHashMapTest {
public static void main(String[] args) {
LinkedHashMap<String,Integer> lhm=new LinkedHashMap<>();
lhm.put("laowang",45);
lhm.put("jack",23);
lhm.put("jim",50);
lhm.put("lucy",28);
System.out.println("lhm的数量:"+lhm.size());
Set<String> keys=lhm.keySet();
Iterator<String> it=keys.iterator();
while (it.hasNext()){
String key=it.next();
Integer value=lhm.get(key);
System.out.println("key="+key+",value="+value);
}
}
}
lhm的数量:4
key=laowang,value=45
key=jack,value=23
key=jim,value=50
key=lucy,value=28
案例二:
将集合变量list中的重复元素去掉,保证如下list集合中添加顺序不变,不能使用循环遍历操作 ArrayList<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("b"); list.add("c"); list.add("b"); list.add("b"); list.add("b"); list.add("c"); list.add("c"); list.add("d"); list.add("c"); list.add("c"); list.add("c"); list.add("d"); list.add("d"); list.add("d"); list.add("d"); list.add("d"); 单独定义方法去除重复,方法定义public void getSingle(ArrayList list); 不能修改变方法的名称返回值和参数
private static void getSingle(List<String> list) { //1,创建一个LinkedHashSet集合 LinkedHashSet<String> lhs = new LinkedHashSet<>(); //2,将List集合中所以的元素添加到LinkedHashSet集合中 lhs.addAll(list); //3,将List集合中的元素清除 list.clear(); //4,将LinkedHashSet集合中元素添加到List集合中 list.addAll(lhs); }
1.5TreeMap
-
TreeMap特点: 可以对Map集合中的元素进行排序。
-
1.TreeMap基于红黑树数据结构的实现
-
2.键可以使用Comparable或Comparator接口, 重写compareTo方法来排序。
-
3.自定义的类必须实现接口和重写方法,否则抛异
-
4.Key值不允许重复,如果重复会把原有的value值覆盖。
-
-
使用元素的自然顺序(字典顺序)进行排序:
-
对象(本身具有比较功能的元素)进行排序。
-
自定义对象(本身没有比较功能的素)进行排序(要进行比较那就让元素具有比较功能,
那就要实现Comparable这个接口里compareTo的方法)
-
-
使用比较器进行排序:
-
定义一个类实现Comparator接口,覆盖compare方法,将类对象作为参数传递给TreeSet集合的构造方法
-
案例 :
package Map;
import com.sun.javafx.collections.MappingChange;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
public class TreeMapTest {
public static void main(String[] args) {
Product p1=new Product("苹果",20);
Product p2=new Product("巧克力",18);
Product p3=new Product("香蕉",25);
//创建外部比较器对象
ProductComparator comparator=new ProductComparator();
//new TreeMap传入外部比较器对象
TreeMap<Product,String>treeMap=new TreeMap<>(comparator);
treeMap.put(p1,"我是苹果");
treeMap.put(p2,"我是巧克力");
treeMap.put(p3,"我是香蕉");
Set<Map.Entry<Product, String>> entries = treeMap.entrySet();
for (Map.Entry<Product, String> entry : entries) {
System.out.println(entry);
}
System.out.println("=======================================");
TreeSet<Product> treeSet = new TreeSet<>(comparator);
treeSet.add(p1);
treeSet.add(p2);
treeSet.add(p3);
for (Product p : treeSet) {
System.out.println(p);
}
}
}
使用外比较器(Comparator())
package Map;
import java.util.Comparator;
public class ProductComparator implements Comparator<Product>{
//判断o1和噢对象大小的规则
//正数 o1小于o2 负数o1大于o2 0 o1等于o2
@Override
public int compare(Product o1, Product o2) {
if (o1.getPrice()<o2.getPrice()){
return 1;
}else if(o1.getPrice()>o2.getPrice()){
return -1;
}else{
return 0;
}
}
}
package Map;
public class Product {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Product{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}