Java基础进阶Map-HashMap集合

简介: Java基础进阶Map-HashMap集合

HashMap集合:


1、HashMap集合底层是哈希表/散列表的数据结构


图解哈希表(引自b站老杜javase):


0a2653c851af460fa595bd959398a8f1.png


自平衡二叉树结构:


2d65d23f6d4748949b924e4057485923.png


2、哈希表是一个怎样的数据结构?


哈希表是一个数组和单向链表的结合体


数组:在查询方面效率很高,随机增删效率方面效率很低


单向链表:在随机增删方面效率较高,在查询方面效率很低


哈希表将以上的两种数据结构融合在一起,充分发挥它们的特点


3、HashMap集合底层


public class HashMap{
//HashMap底层实际上就是一个数组(一维数组)
Node<K,V>[] table;
//静态的内部类HashMap.Node
static class Node<K,V>{
final int hash;//哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换成存储数组的下标)
final K key;//存储到Map集合中的那个key
V value;//存储到Map集合中的那个value
Node<K,V> next;//下一个节点的内存地址
}
}


哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合)


4、最主要掌握的是:


map.put(k,v)
v = map.get(k)


以上这两个方法的实现原理,掌握


5、HashMap集合的key部分特点:


无序,不可重复。


为什么无序?因为不一定挂到哪个单向链表上


不可重复是怎么保证的?equals方法来保证HashMap集合的key不可重复


如果key重复了,values会覆盖


放在HashMap集合key部分元素其实就是放到HashSet集合中了


所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。


7 % 4 = 3,11 % 4 = 3(hash值不同时,通过hash算法转换的下标有可能一样,所以必须重写hashCode方法,Object中的hashCode方法返回对象的内存地址转换成hash值)


6、哈希表HashMap使用不当时无法发挥性能!


假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们称为:散列分布不均匀


什么是散列分布不均匀?


假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的。是散列分布均匀的

假设将多有的hashCode()方法返回值都设定为不一样的值,可以吗?有什么问题?不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念也是散列分布不均匀

散列分布均匀需要你重写hashCode()方法时有一定的技巧


7、重点:


放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法


8、HashMap集合的默认初始化容量是16,默认加载因子是0.75


这个默认加载因子是当HashMap集合底层数组的容量达到是75%的时候,数组开始扩容


重点,记住:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的。这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。


示例代码01:


public class HashMapTest01 {
    public static void main(String[] args) {
        //创建HashMap集合对象
        //测试HashMap集合key部分的元素特点的
        //Integer是key,它的hashCode和equals都重写了
        Map<Integer,String> map = new HashMap<>();
        //往集合中添加元素
        map.put(1111,"zhangsan");
        map.put(6666,"lisi");
        map.put(7777,"wangwu");
        map.put(2222,"zhaoliu");
        map.put(2222,"zhaoliu");//key重复的时候value会自动覆盖
        //遍历集合
        Set<Map.Entry<Integer, String>> set = map.entrySet();
        for(Map.Entry<Integer, String> entry : set){
            //验证结果:HashMap集合key部分元素:无序不可重复
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
    }
}


运行结果:


6de278e6d6694ce5bb08e7e842b7e74b.png


9、向Map集合中存,以及从Map集合中取


都是先调用key的hashCode方法,然后再调用equals方法!


equals方法有可能调用,也有可能不调用。


拿put(k,v)举例,什么时候equals不会调用?


k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标,

数组下标位置上如果是null,equals不需要执行

拿get(k)举例,什么时候equals不会调用?


k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标

数组下标位置上如果是null,equals不需要执行


10、注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写


并且equals方法返回如果是true,hashCode()方法返回的值必须一样


equals方法返回true表示两个对象相同,在同一个区链表上比较


那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的


所以hashCode()方法的返回值也应该相同


hashCode()方法和equals方法不用研究,直接使用IDEA工具生成,但是两个方法要同时生成


11、终极结论:


放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法


12、对于哈希表数据结构来说:


如果o1和o2的hash值相同,一定是放到同一个单向链表上


当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”

jdk7HashMap底层数据结构是数组 + 链表

jdk8HashMap底层数据结构是数据 + 链表 + 红黑树

当数组长度>64并且链表中的元素个数>8时,链表转换成红黑树结构

当红黑树中的节点个数<6时,红黑树转换成链表结构


示例代码02:


public class HashMapTest02 {
    public static void main(String[] args) {
        Student s1 = new Student("zhagnsan");
        Student s2 = new Student("zhagnsan");
        System.out.println(s1.equals(s2));//没重写equals之前是false,重写之后是true
        //输出两个对象的hash值
        System.out.println("s1的哈希值是:" + s1.hashCode());//1163157884
        System.out.println("s1的哈希值是:" + s2.hashCode());//1956725890
        //创建HashSet集合对象
        // s1.equals(s2)结果已经是true了,表示s1和s2是一样的,相同的,那么往HashSet集合中放的话,
        // 按说只能放进去1个。(HashSet集合特点:无序不可重复)
        Set<Student> students = new HashSet<>();// 这个结果按说应该是1. 但是结果是2.显然不符合HashSet集合存储特点。怎么办?
        students.add(s1);
        students.add(s2);
        System.out.println(students.size());
    }
}

运行结果:


true

s1的哈希值是:1163157884

s1的哈希值是:1956725890


13.HashMap集合key部分允许null吗?


允许

但是要注意:HashMap集合的key null值只能有一个。


示例代码03:


public class HashMapTest03 {
    public static void main(String[] args) {
        Map map = new HashMap();
        //向集合中添加null的key和value
        map.put(null,null);
        System.out.println(map.size());//1
        //再次向集合中添加null的value,和不为null的key
        map.put(null,100);
        System.out.println(map.size());//1
        //通过key获取value
        System.out.println(map.get(null));//100
    }
}
相关文章
|
2月前
|
Java
Java之HashMap详解
本文介绍了Java中HashMap的源码实现(基于JDK 1.8)。HashMap是基于哈希表的Map接口实现,允许空值和空键,不同步且线程不安全。文章详细解析了HashMap的数据结构、主要方法(如初始化、put、get、resize等)的实现,以及树化和反树化的机制。此外,还对比了JDK 7和JDK 8中HashMap的主要差异,并提供了使用HashMap时的一些注意事项。
Java之HashMap详解
|
2天前
|
存储 安全 Java
Java 集合框架中的老炮与新秀:HashTable 和 HashMap 谁更胜一筹?
嗨,大家好,我是技术伙伴小米。今天通过讲故事的方式,详细介绍 Java 中 HashMap 和 HashTable 的区别。从版本、线程安全、null 值支持、性能及迭代器行为等方面对比,帮助你轻松应对面试中的经典问题。HashMap 更高效灵活,适合单线程或需手动处理线程安全的场景;HashTable 较古老,线程安全但性能不佳。现代项目推荐使用 ConcurrentHashMap。关注我的公众号“软件求生”,获取更多技术干货!
18 3
|
19天前
|
存储 缓存 安全
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
37 5
|
2月前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
42 4
|
2月前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
35 2
|
2月前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
2月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
2月前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
|
2月前
|
Java 开发者
|
2月前
|
存储 Java 开发者
Java中的集合框架深入解析
【10月更文挑战第32天】本文旨在为读者揭开Java集合框架的神秘面纱,通过深入浅出的方式介绍其内部结构与运作机制。我们将从集合框架的设计哲学出发,探讨其如何影响我们的编程实践,并配以代码示例,展示如何在真实场景中应用这些知识。无论你是Java新手还是资深开发者,这篇文章都将为你提供新的视角和实用技巧。
36 0