Map集合

Map

map集合:

  • map集合是存放键值对数据的容器
  • vaLue是key 的一个替代品,我们以后在操作元素时,是根据key去操作
  • map集合的key是不允许重复的,如果在添加元素时,key的值重复,那么后添加的元素会把前面的key覆盖掉
  • value的值可以重复

map集合中的方法:

嵌套类
static interface Map.Entry<K,V> 映射项(键-值对)。
方法摘要
void void clear() 从此映射中移除所有映射关系(可选操作)。
boolean containsKey()(Object key) 如果此映射包含指定键的映射关系,则返回 true
boolean containsValue()(Object value) 如果此映射将一个或多个键映射到指定值,则返回 true
Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的映射关系的 Set 视图。
boolean equals()(Object o) 比较指定的对象与此映射是否相等。
V get()(Object key) 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
int hashCode() 返回此映射的哈希码值。
boolean isEmpty() 如果此映射未包含键-值映射关系,则返回 true
Set<K> keySet() 返回此映射中包含的键的 Set 视图。
V put]()(K key, V value) 将指定的值与此映射中的指定键关联(可选操作)。
void putAll()(Map<? extends K,? extends V> m) 从指定映射中将所有映射关系复制到此映射中(可选操作)。
V remove()(Object key) 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
int size() 返回此映射中的键-值映射关系数。
Collection<V> values() 返回此映射中包含的值的 Collection 视图。

Map集合的遍历:

  • keySet()遍历key和value:

    Map map=new HashMap();
    map.put("name","Tom");
    map.put("age","23");
    map.put("address","beijing");
    map.put("idCard","21324354");
    
    for (String key : map.keySet()) {
            System.out.println("key= "+ key + " and value= " + map.get(key));
        }
    
  • keySet()方法:将所有的key转换成一个Set集合通过Set的迭代器遍历

    //将所有的Key转成Set集合
    Set set = map.keySet();
    //获取set集合的迭代器对象
    Iterator iterator = set.iterator();
    while (iterator.hasNext())
            {
                Object key = iterator.next();
                //获得key后,根据map.get()方法获取value
                Object value = map.get(key);
                System.out.println(key+"="+value);
                //可以发现Set集合存入和取出的顺序是无序的
            }
    
  • Set<Map.Entry<K,V>> entrySet() 方法:

    Map中是以键值对的形式存放数据,Entry是Map中的一个内部接口,Map用Entry来表示一个键值对,一个键值对也就是一个Entry。

    Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的映射关系的 Set 视图。即是返回一个存放类型为键值对(Entry)的Set集合,Entry接口内部还有本身的getKey()和getValue()方法,Entry.getKey()就相当于键值对调用它获取Key的方法。

    //将Map集合转成Set集合
    Set<Map.Entry<String ,String >> entries= map1.entrySet();
    for (Map.Entry<String, String> entry : entries) {
         //获取Key的值
         String key = entry.getKey();
         String value = entry.getValue();
         System.out.println(key+"="+value);
    }
    

HashMap的存储过程

​ 我们添加到集合中的元素如果是引用数据类型,那么一定会继承Object类中的hashCode()方法。首先,HashMap集合会调用put()来添加元素,这时候HashMap底层会通过hashCode()方法随机生成hash值,根据这个hash值来计算该元素应该存在哪个单元下面。确定了存放在哪个单元,这时候会通过equals()方法,用key的引用与该单元下所有的Node节点进行比较(该引用数据类型如果重写了equals()方法,那么比较的是对应属性中的内容,如果没有重写,那么比较的是地址值),如果equals()方法比较结果是true,那么新添加进单元的节点,将会覆盖点该单元下旧的节点,如果比较结果为false,那么该节点将会放在该单元的单链表的尾部节点。

​ 举个栗子:

  • 有一个学生类
public class Student {
    private  String name;
    private  Integer age;
public Student(String name, Integer age) {
    this.name = name;
    this.age = age;
}

    /*注意这里重写了equals方法,并且我们现在假设认为如果学生的名字和年龄相同那么这就是同一个学生*/
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) 				
            return false;
    Student student = (Student) o;
    return Objects.equals(name, student.name) &&
            Objects.equals(age, student.age);
}
}
//主函数测试类
HashMap<Student, Object> hashMap = new HashMap<>();
hashMap.put(new Student("kas",20),12);
hashMap.put(new Student("kas",20),12);
hashMap.put(new Student("kas",20),12);
hashMap.put(new Student("kas",20),12);

System.out.println(hashMap.size());

​ 输出结果:

4

​ 结果分析:

  • HashMap中是不允许有重复的元素,而上面put()了4个相同的student,尽管对equals()方法已经重写了,但是我们得到的结果是hashmap大小为4。这是因为我们没有对hashCode()方法重写。根据上述HashMap的存储过程说明,在put()进元素的时候底层会先根据hashCode()方法产生hash值来先确定存放到哪一个单元,我们并没有重写hashCode(),所以默认继承一个Object类的hashCode()方法,产生的hash值是随机的,也就是说这些相同的Student,在一开始就可能被分配到不同单元去了,那么即使重写了equals()方法,在每个单元进行equals()比较的时候,自然也是没有重复的。所以会认为不是重复的元素,并不进行覆盖。我们可以通过输出hashCode的值来观察是否被分配到了不同单元。

    System.out.println(new Student("kas",20).hashCode());
    System.out.println(new Student("kas",20).hashCode());
    System.out.println(new Student("kas",20).hashCode());
    System.out.println(new Student("kas",20).hashCode());
    

​ 输出结果:

21685669
2133927002
1836019240
325040804

​ 现在我们在Student类中重写hashCode()方法,再做相同的操作:

@Override
public int hashCode() {
    return Objects.hash(name, age);
}
HashMap<Student, Object> hashMap = new HashMap<>();
hashMap.put(new Student("kas",20),12);
hashMap.put(new Student("kas",20),12);
hashMap.put(new Student("kas",20),12);
hashMap.put(new Student("kas",20),12);

System.out.println(hashMap.size());

System.out.println(new Student("kas",20).hashCode());
System.out.println(new Student("kas",20).hashCode());
System.out.println(new Student("kas",20).hashCode());
System.out.println(new Student("kas",20).hashCode());

​ 输出结果:

1
3285400
3285400
3285400
3285400

判断为相等(equals)的对象,那么他们的hashCode必须要具有相同的hash值。

hashCode()方法重写之后,要认为相等的类,他们的hash值相同。

关于hashCode()方法和equals()方法的理解:

  • 一般重写了equals()方法同时还要重写hashCode()方法,hash又叫散列,是结合数组和链表两种数据结构形成的一种hash散列表,重写了equals()方法,仅仅是保证了同一数组下,链表中不同节点值相同,如果没有重写hashCode()方法,那么当我们认为应该相同的类的对象,在继承Object类的hashCode()方法生成的hash值一般是不同的,这意味着在一开始他们就被分配到不同的hash桶中了,在不同的hash桶中equals()自然返回值为false。所以,相等的对象,他们的hashCode值必须要相同。而hashCode值相同的对象,他们不一定相等。java中的hash函数返回的是int类型的,也就是说,最多允许存在2^32个分组,也是有限的,所以也就也可能出现相同的哈希值。

  • 一般来说,如果你要把一个类的对象放入容器中,那么通常要为其重写equals()方法,让他们比较地址值而不是内容值。特别地,如果要把你的类的对象放入散列中,那么还要重写hashCode()方法;要放到有序容器(比如 TreeMap)中,还要重写compareTo()方法。

HashTable

HashTable与HashMap

​ HashTable就是线程安全版的HashMap,HashTable是线程安全的,HashMap是线程不安全的;HashMap可以有一个key为空(因为多个空会覆盖),HashTable的key和value都不允许为空。跟ArrayList和Vector是一样的,HashMap的执行效率都比HashTable要高,因为所有的HashTable方法都加了线性同步关键字,这样的话在多线程场景下,执行效率不高,但是解决了线程同步的问题。而一般在解决线程安全的问题采用别的途径,所以HashMap这个集合一般是使用得比较多的集合。

HashMap<Object, Object> HashMap = new HashMap<>();
Hashtable<Object, Object> Hashtable = new Hashtable<>();
HashMap.put(null,null);
HashMap.put(null,null);
HashMap.put(null,null);
HashMap.put(null,5);
/*HashMap的值不可以重复 所以存放一个null后,其他的再加null会覆盖掉,因此相当于存放的是最后一个放入的<null,5>,因此,输出结果为 1 */
System.out.println("hashmap的长度为"+HashMap.size());

Hashtable.put(null,1);
Hashtable.put(null,2);
Hashtable.put(null,3);
Hashtable.put(null,4);
//这里运行会报错因为HashTable的key和value都不允许为null
System.out.println("hashmap的长度为"+Hashtable.size());

TreeMap

​ TreeMap是可以按照Key排序的集合,底层是二叉树的数据结构,可以根据不同的Key的数据类型,不同的排序(前提是该数据类型实现了Comprable接口,重写了compareTo()方法)。

  • Integer:从小到大排序

  • String:首字母的从小到大排序

  • 其他:若是自定义的类,要加入TreeMap集合,必须要求实现了Comprable接口,重写了compareTo()方法,因为TreeMap底层源码

    要进行强制类型转换。

    return comparator==null ? ((Comparable<? super K >)k1).compareTo((K)k2) : comparator.compare((K)k1, (K)k2);
    
  • 举个栗子:

    一个货物类要根据价格进行排序

    public class Goods implements Comparable<Goods>{
    private Double price;
    public Goods(Double price) {
            this.price = price;
        } 
    //方法重写
    @Override
    public int compareTo(Goods g) {
    
        //这样结果会是降序排列,详情查看源码
        return (int)(g.price - this.price);
    }
    }
    
    TreeMap<Goods,Object> treeMap2=new TreeMap<>();
    treeMap2.put(new Goods(12.5),11);
    treeMap2.put(new Goods(14.3),11);
    treeMap2.put(new Goods(15.5),11);
    treeMap2.put(new Goods(10.2),11);
    for (Map.Entry<Goods, Object> integerObjectEntry : treeMap2.entrySet()) {
    
        System.out.println(integerObjectEntry.getKey()+"="+integerObjectEntry.getValue());
    }
    

一定在要在Goods类中实现Comparable接口,重写compareTo()方法,不重写会报类型转换异常。根据源码

return comparator==null ? 
    ((Comparable<? super K>)k1).compareTo((K)k2):comparator.compare((K)k1, (K)k2);
posted @ 2021-07-24 19:18  ins1mnia  阅读(39)  评论(0编辑  收藏  举报