Java集合框架详解(四)——Map接口、HashMap类、LinkedHashMap类

一、Map接口

  1. Map接口的特点:

(1)映射键值对的形式(key和value);
(2)Map集合中,key是不能重复的,value是可以重复的;
(3)Map集合是散列存放数据的,所以存储顺序与遍历顺序是无序的,即遍历顺序可能与存储顺序不一致。

  1. Map集合的获取方法:
方法名称说明
V get(Object key)根据key获取值
Set keySet()获取所有健的集合
Collection values()获取所有值的集合
Set<Map.Entry<K,V> entrySet()获取所有健值对象的集合
default V getOrDefult(Object key, V defaultValue)如果存在相应的key则返回其对应的value,否则返回给定的默认值defaultValue
eg:
public static void main(String[] args) {
        Map<String, String> hashMap = new HashMap<>();
        hashMap.put("qize01", "小丽");
        hashMap.put("qize02", "天天");
        hashMap.put("qize03", "阿奇");
        hashMap.put("qize04", "毛毛");
        //1、根据键获取值
        System.out.println(hashMap.get("qize01"));
        System.out.println(hashMap.get("ss"));
        //2、获取所有健的集合
        Set<String> stringSet = hashMap.keySet();
        for (String key:stringSet){
            System.out.println(key);
        }
        //3、获取所有value的集合
        Collection<String> values = hashMap.values();
        for (String value:values){
            System.out.println(value);
        }
        //4、获取所有键值对象的集合
        Set<Map.Entry<String, String>> entrySet = hashMap.entrySet();
        for (Map.Entry<String, String> entry:entrySet){
            System.out.println(entry);
        }
        //5、如果存在相应的key,则返回其对应的value,否则返回给定的默认值defaultValue
        String qize01 = hashMap.getOrDefault("qize0001","默认值");
        System.out.println(qize01);
 }

二、HashMap类

1、HashMap类特点:
(1)HashMap类实现Map接口;
(2)HashMap类是散列存放数据的,所以存储顺序与遍历顺序是无序的,即遍历顺序可能与存储顺序不一致。
(3)HashMap:底层是基于数组+链表结构来实现的(JDK1.7);基于数组+链表+红黑树实现的(JDK1.8);是线程不安全的。

eg:

Map<String,String> hashMap = new HashMap<>();
hashMap.put("qize001","天天");
hashMap.put("qize002","小丽");
hashMap.put("qize003","莱德");
hashMap.put("qize004","古薇");
System.out.println(hashMap);

2、HashMap类常用方法

方法名称解释
V put(K key, V value)添加元素
V remove(K key, V value)根据健值删除对应的值
void clear()清除所有健值元素
boolean containsKey(Object key)判断集合中是否包含指定的健
boolean containsValue(Object value)判断集合中是否包含指定的值
boolean isEnpty()判断集合是否为空

eg:

public static void main(String[] args) {
	Map<String, String> hashMap = new HashMap<>();
    //put方法
    hashMap.put("qize001","天天");
    hashMap.put("qize002","阿奇");
    hashMap.put("qize003","小丽");
    System.out.println(hashMap);
    System.out.println("------------------");
    //判断集合中是否包含指定的健
    System.out.println(hashMap.containsKey("qize002"));
    //判断集合中是否包含指定的值
    System.out.println(hashMap.containsValue("小丽"));
    //根据健值删除对应的值
    hashMap.remove("qize002");
    System.out.println(hashMap);
    //判断集合是否为空
    System.out.println(hashMap.isEmpty());
    //清除所有健值元素
    hashMap.clear();
    System.out.println(hashMap);
    //判断集合是否为空
    System.out.println(hashMap.isEmpty());
}

3、HashMap集合的遍历
(1)先取到HashMap集合中所有的键,再通过增强for循环调用get方法获取对应的value值:
eg:

public static void main(String[] args) {
    HashMap<String,String> hashMap = new HashMap<>();
    hashMap.put("qize01","毛毛");
    hashMap.put("qize02","天天");
    hashMap.put("qize03","阿奇");
    Set<String> keySet = hashMap.keySet();
    for (String key:keySet){
        String value = hashMap.get(key);
        System.out.println(key+"-"+value);
    }
}

(2)使用entrySet() 方法获取所有健值对象的集合
eg:

public static void main(String[] args) {
    HashMap<String,String> hashMap = new HashMap<>();
    hashMap.put("qize01","毛毛");
    hashMap.put("qize02","天天");
    hashMap.put("qize03","阿奇");
    Set<Map.Entry<String, String>> entrySet = hashMap.entrySet();
    for(Map.Entry<String, String> entry:entrySet){
        System.out.println(entry.getKey()+"-"+entry.getValue());
    }
 }

(3)使用entrySet() 方法和 Iterator 获取所有健值对象的集合
eg:

public static void main(String[] args) {
    HashMap<String,String> hashMap = new HashMap<>();
    hashMap.put("qize01","毛毛");
    hashMap.put("qize02","天天");
    hashMap.put("qize03","阿奇");
    Set<Map.Entry<String, String>> entrySet = hashMap.entrySet();
    Iterator<Map.Entry<String, String>> iterator = entrySet.iterator();
    while (iterator.hasNext()){
        Map.Entry<String, String> entry = iterator.next();
        System.out.println(entry.getKey()+"-"+entry.getValue());
    }
}

4、HashMap集合的底层原理
(1)HashMap 的 key 是否可以设置为自定义对象?可以。
(2)HashMap 存储数据时有序还是无序的?是无序的。
(3)HashMap 是否可以存放null 值?如果可以的话,存放在数组中哪个位置呢?可以存放null值,存放在数组index为0的位置。
(4)HashMap 集合中键值对是如何封装的?是通过实现Map接口封装的Entry对象来封装的。
eg:

public static void main(String[] args) {
    //HashMap 的 key 是否可以设置为自定义对象?可以。
    Student student = new Student("qize01",32);
    HashMap<Student, String> hashMap1 = new HashMap<>();
    hashMap1.put(student, "qize");
    //HashMap 存储数据时有序还是无序的?是无序的。
    HashMap<String, String> hashMap2 = new HashMap<>();
    for (int i=0;i<20; i++){
        hashMap2.put("qize"+i, "i:"+i);
    }
    hashMap2.forEach((k,v) -> {
        System.out.println(k+","+v);
    });
    //HashMap 是否可以存放null 值?如果可以的话,存放在数组中哪个位置呢?
    //可以存放null值,存放在数组index为0的位置
    HashMap<String, String> hashMap3 = new HashMap<>();
    hashMap3.put(null,"毛毛");
}

(5)key的hash算法原理
hashMap集合 1.7版本 是基于数组+链表实现的
hashMap集合 1.8版本 是基于数组+链表+红黑树实现的
根据key的hashCode 取余entrys.length,得到的余数就是该key存放在我们数组中对应的index位置
eg:

public class QizeHashMap<K, V> {
    private  Entry[] entrys = new Entry[10000];

    class Entry<K, V> {
        K k;//key
        V v;//value

        int hash;//key的hash值

        public Entry(K k, V v) {
            this.k = k;
            this.v = v;
        }
    }
    
    public  void put(K k, V v){
        //根据key的hashCode 取余entrys.length
        //得到的余数就是该key存放在我们数组中对应的index位置
        int index = k.hashCode() % entrys.length;
        entrys[index] = new Entry<>(k, v);
    }

    public V get(K k){
        //如果根据key查询的话,hashCode 取余entrys.length
        int index = k.hashCode() % entrys.length;
        return (V) entrys[index].v;
    }

    public static void main(String[] args) {
        QizeHashMap<Object, Object> hashMap = new QizeHashMap<>();
        hashMap.put("qize", "毛毛");
        System.out.println(hashMap.get("qize"));
    }
}

(6)什么是hash冲突问题?
hashCode相同但是值不同,例如 key=“a” 和key=97 最终计算的存放value的下标index相同,则key=97的存放在数组的键值对(Entry对象)会覆盖掉key="a"存放在数组中的键值对。
eg:

//在(5)key的hash算法原理的例子的基础上修改main方法,如下
public static void main(String[] args) {
    QizeHashMap<Object, Object> hashMap = new QizeHashMap<>();
    hashMap.put("a", "毛毛");
    hashMap.put(97, "小丽");
    System.out.println(hashMap.get("a"));
}

运行得到的打印结果为 “小丽” ,而不是 “毛毛” ,这就是hash冲突问题。
在这里插入图片描述
怎么解决hash冲突问题?
在JDK 1.7中是通过链表来解决的。
先判断该index位置 是否有存放键值对(Entry对象),如果取出结果为null,则表示此index位置没有存放数据,则可以直接存放,如果取出的结果为一个Entry对象,则表示此处有hash冲突,需要进行处理。
故上述“(5)key的hash算法原理的例子”可以优化为如下:

public class QizeHashMap<K, V> {
    private  Entry[] entrys = new Entry[10000];

    class Entry<K, V> {
        K k;//key
        V v;//value

        int hash;//key的hash值
        Entry<K, V> next;//Entry的下一个节点

        public Entry(K k, V v, int hash) {
            this.k = k;
            this.v = v;
            this.hash = hash;
        }
    }

    public  void put(K k, V v){
        //根据key的hashCode 取余entrys.length
        //得到的余数就是该key存放在我们数组中对应的index位置

        /**
         * 1、先判断该index位置 是否有存放数据
         * 2、如果取出结果为null,则表示此index位置没有存放数据,则可以直接存放
         * 3、如果取出的结果为一个Entry对象,则表示此处有hash冲突,需要进行处理
         */
        int hash = k.hashCode();
        int index = hash % entrys.length;
        Entry oldEntry = entrys[index];
        if(oldEntry == null){
            entrys[index] = new Entry<>(k, v, hash);
        }else {
            oldEntry.next = new Entry<>(k, v, hash);
        }

    }

    public V get(K k){
        //如果根据key查询的话,hashCode 取余entrys.length
        int hash = k.hashCode();
        int index = hash % entrys.length;
        for (Entry<K, V> entry = entrys[index]; entry != null; entry = entry.next){
            if(entry.hash == hash && (entry.k == k || entry.k.equals(k))){
                return entry.v;
            }
        }
        return null;
    }

    public static void main(String[] args) {
        QizeHashMap<Object, Object> hashMap = new QizeHashMap<>();
        hashMap.put("a", "毛毛");
        hashMap.put(97, "小丽");
        System.out.println(hashMap.get("a"));
    }
}

三、hashCode方法与equals方法

  1. 如果两个对象equals相等,那么这两个对象的HashCode一定也相同;
  2. 如果两个对象的hashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置;
  3. 如果对象的equals方法被重写,那么对象的hashCode方法也尽量重写;
  4. String的equals方法验证的是两个字符串的值是否一样。

eg:

public class Student {
    String name;
    int age;

    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) {
        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 Objects.hash(name, age);
    }
}
public static void main(String[] args) {
    /**
     *  equals 属于 object父类中,默认的情况下 在比较两个对象的内存地址是否相同
     *  两个字符串equals 是比较两个对象的值是否相等,因为String类重写了equals方法
     */
    String str1 = "qize";
    String str2 = "qize";
    System.out.println(str1.equals(str2));
    System.out.println("--------------------");
    Student student1 = new Student("qize",22);
    Student student2 = new Student("qize",22);
    System.out.println(student1.equals(student2));
    System.out.println(student1.hashCode());
    System.out.println(student2.hashCode());
}

四、LinkedHashMap集合

LinkedHashMap 继承自HashMap,它的多种操作都是建立在HashMap操作的基础上的,与HashMap不同的是,LinkedHashMap 维护了一个Entry 的双向链表,保证了插入的Entry 中的顺序。
eg:

public static void main(String[] args) {
    LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
    for(int i=1; i<=100; i++){
        linkedHashMap.put("k:"+i,"v:"+i);
    }
    for (Map.Entry<String, String> entry:linkedHashMap.entrySet()){
        System.out.println(entry.getKey()+","+entry.getValue());
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吟诗作对歌一曲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值