1.hashmap的set为啥可以去重底层原理是啥?
HashMap的set(集合)可以去重是因为其底层原理是基于哈希表(Hash Table)实现的。
在HashMap中,元素存储的位置是根据元素的哈希码(Hash Code)来确定的。当向HashMap中添加元素时,首先会计算元素的哈希码,并根据哈希码找到对应的存储位置,然后将元素存储在该位置上。
在存储过程中,如果发现两个元素的哈希码相同,即发生了哈希冲突(Hash Collision),HashMap会采用链表或红黑树等数据结构来解决冲突。具体而言,当多个元素的哈希码映射到同一个位置时,它们会以链表的形式存储在该位置上。当链表长度达到一定阈值时,链表会转换为红黑树,以提高查找效率。
由于HashMap的特性,它不允许存在重复的键(Key)。当我们向HashMap中添加键值对时,它会先判断键是否已经存在。如果键已经存在,则新的值会覆盖旧的值;如果键不存在,则会将键值对添加到HashMap中。
因此,通过HashMap的内部机制,它能够快速地判断键是否已经存在,从而实现了去重的功能。
2.HashMap是Java中常用的集合类,它实现了Map接口,用于存储键值对。在Java 1.7和1.8版本中,HashMap的底层实现发生了一些变化。
Java 1.7中的HashMap底层使用数组+链表的数据结构来存储键值对。当多个键映射到同一个桶(数组的某个位置)时,这些键值对会以链表的形式存储在桶中。这种实现方式在处理冲突时比较简单,但在链表长度过长时,会导致查找效率下降。
Java 1.8中的HashMap进行了优化,引入了数组+链表+红黑树的数据结构。当链表长度超过一定阈值(默认为8)时,链表会转换为红黑树,以提高查找、插入和删除操作的性能。这种优化主要针对链表过长的情况,当链表长度较短时,仍然使用链表结构。
另外,Java 1.8中的HashMap还引入了扩容机制的改进。在扩容时,原有的数组容量会翻倍,并将原有的键值对重新计算哈希值并分配到新的桶中。这样可以减少哈希碰撞的概率,提高HashMap的性能。
综上所述,Java 1.8中的HashMap相比于1.7版本,在处理冲突时引入了红黑树结构,提高了查找、插入和删除操作的性能;同时,在扩容机制上也进行了改进,减少了哈希碰撞的概率。
3.HashMap和ConcurrentHashMap是Java中两种常见的Map实现类,它们在多线程环境下的并发性能上有一些区别。
-
线程安全性:HashMap是非线程安全的,不适合在多线程环境下使用。如果多个线程同时对HashMap进行修改操作,可能会导致数据不一致或抛出异常。而ConcurrentHashMap是线程安全的,通过使用锁分段技术(Segment)来实现并发访问控制,不同的段可以被不同的线程同时访问,提高了并发性能。
-
性能表现:在单线程环境下,HashMap的性能通常比ConcurrentHashMap好,因为ConcurrentHashMap需要维护额外的并发控制机制。但在高并发的多线程环境下,ConcurrentHashMap的性能往往优于HashMap,因为它允许多个线程同时读取数据,而不需要加锁。
-
迭代器弱一致性:HashMap的迭代器是快速失败的,即在迭代过程中,如果其他线程对HashMap进行修改,会抛出ConcurrentModificationException异常。而ConcurrentHashMap的迭代器是弱一致性的,它不会抛出异常,可以在迭代过程中安全地进行修改操作。
-
空值和空键:HashMap允许存储一个null值和多个null键,而ConcurrentHashMap不允许存储null值和null键。
综上所述,如果在多线程环境下需要进行并发操作,并且对数据的一致性要求较高,可以选择使用ConcurrentHashMap。而在单线程环境下或者对并发性能要求不高的情况下,可以选择使用HashMap。
HashMap的get方法
1,根据hash方法获取到key的hash值。
2,通过hash & (length - 1)的方式获取到key所对应的Node数组下标(length对应数组长度)。
3,判断此结点是否为空,是否就是要找的值,是则返回空,否则进入第二个结点。
4,如果第二个结点不为空,则继续判断该结点是否为TreeNode类型,如果是,则继续遍历该结点的子树,直到找到值为val的元素或者遍历完整个树形结构。
5,如果找到了值为val的元素,则返回该元素所在的节点;否则返回null。