集合有哪些?
- List(列表):
- ArrayList:基于动态数组实现,支持快速随机访问,但插入和删除操作效率较低。
- LinkedList:基于双向链表实现,插入和删除操作效率较高,但随机访问效率较低。
- Vector:与ArrayList类似,但是是线程安全的,性能相对较低。
- Set(集):
- HashSet:基于哈希表实现,不保证元素的顺序,不允许重复元素。
- LinkedHashSet:在HashSet的基础上使用双向链表维护元素的插入顺序。
- TreeSet:基于红黑树实现,元素按照自然顺序或自定义比较器排序,不允许重复元素。
- Queue(队列):
- LinkedList:双向链表实现,可用作队列和栈。
- PriorityQueue:基于堆实现的优先队列。
- Map(映射):
- HashMap:基于哈希表实现,使用键值对存储数据,不保证顺序,允许使用null键和null值。
- LinkedHashMap:在HashMap的基础上使用双向链表维护元素的插入顺序或访问顺序。
- TreeMap:基于红黑树实现,按照键的自然顺序或自定义比较器排序。
- Stack(栈):
- Stack:基于Vector实现的栈,后进先出(LIFO)。
以上是Java中常见的集合类,每种集合类都有其适用的场景和特点,可以根据具体的需求选择合适的集合类来存储和操作数据。
ArrayList和LinkedList的区别。
ArrayList和LinkedList是Java中两种常见的列表(List)实现,它们有以下区别:
- 内部实现结构:
- ArrayList:基于动态数组实现,使用数组来存储元素,可以随机访问数组中的元素。
- LinkedList:基于双向链表实现,每个节点包含一个元素和对前后节点的引用,插入和删除操作效率较高,但随机访问元素的效率较低。
- 访问效率:
- ArrayList:由于基于数组实现,支持根据索引进行快速随机访问,时间复杂度为O(1)。
- LinkedList:由于基于链表实现,随机访问需要从头或尾部开始遍历到指定位置,时间复杂度为O(n),其中n为元素的数量。
- 插入和删除效率:
- ArrayList:在末尾进行插入和删除操作效率较高,时间复杂度为O(1)。但在列表中间进行插入和删除操作,需要移动后续元素,时间复杂度为O(n)。
- LinkedList:由于基于链表实现,插入和删除操作只需修改节点的引用,效率较高,时间复杂度为O(1)。
- 内存占用:
- ArrayList:由于使用动态数组存储元素,除了实际存储的元素外,还会有一定的空间浪费,因为需要预留一定的空间作为缓冲区。
- LinkedList:由于使用链表存储元素,除了实际存储的元素外,还需要额外的空间来存储节点的引用,所以相对于ArrayList来说,占用的内存空间较多。
综上所述,当需要频繁地进行插入和删除操作时,LinkedList的效率更高。而当需要频繁地进行随机访问操作时,ArrayList的效率更高。因此,在选择ArrayList和LinkedList之间时,需要根据具体的需求和操作场景来决定使用哪种实现。
JDK1.7到JDK1.8中HashMap发生了什么变化?
在JDK 1.7和JDK 1.8之间,HashMap在内部实现上发生了一些变化,主要包括以下几点:
- 数据结构:
- JDK 1.7中的HashMap采用数组+链表的数据结构来存储键值对。当发生哈希冲突时,使用链表将冲突的元素链接在一起。
- JDK 1.8中的HashMap引入了红黑树,即链表长度超过一定阈值(默认为8)时,将链表转换为红黑树,以提高在哈希冲突较多的情况下的查找效率。
- 存储方式:
- JDK 1.7中的HashMap在每个数组元素上都存储了一个Entry链表的头节点,即使在该链表上没有发生哈希冲突。
- JDK 1.8中的HashMap对于没有发生哈希冲突的数组元素,不再存储头节点,而是使用一个空的特殊节点来表示空槽。
- 扩容机制:
- 扩容机制有所不同。
这些变化的目的是为了提高HashMap在哈希冲突较多的情况下的查找效率,减少链表的长度,进而提升性能。通过引入红黑树来替代过长的链表,可以在最坏情况下将查找的时间复杂度从O(n)降低到O(log n)。同时,在存储空间上也有所改进,减少了空槽的占用。
需要注意的是,虽然JDK 1.8对HashMap进行了优化,但在实际应用中,还是需要根据具体的场景和需求选择合适的数据结构和容量,以达到最佳的性能和效果。
说下HashMap的Put方法。
HashMap的put(key, value)
方法用于将指定的键值对存储到HashMap中。下面是HashMap的put
方法的简要描述:
- 首先,将传入的
key
通过hashCode()
方法计算哈希码(hash code),以确定该键值对在数组中的位置。 - 接着,通过哈希码和数组长度取模的方式,确定该键值对应的数组索引位置。如果该位置上没有元素,直接将键值对存储到该位置。
- 如果该位置已经存在元素(可能是链表或红黑树的头节点),则需要进行进一步的操作:
- 如果该位置上的元素是链表,则遍历链表,根据键的
equals()
方法判断是否存在相同的键。如果找到相同的键,则替换对应的值。如果遍历完链表仍未找到相同的键,则将新的键值对添加到链表的末尾。 - 如果该位置上的元素是红黑树,则通过红黑树的插入操作,将键值对插入到红黑树中。如果红黑树发生结构变化(如需要转换为链表),则可能需要进行相应的调整。
- 如果该位置上的元素是链表,则遍历链表,根据键的
- 在插入键值对后,检查HashMap中的元素个数是否超过负载因子与当前容量的乘积,如果超过则进行扩容操作。
总结来说,HashMap的put
方法通过哈希码计算数组索引位置,处理哈希冲突,处理链表和红黑树,以及进行扩容操作,确保键值对能够正确地存储在HashMap中。
HashMap的扩容机制原理。
HashMap的扩容机制是为了保持负载因子(Load Factor)在一个可接受的范围内,以保证HashMap的性能和空间利用率。当HashMap中的元素数量超过负载因子与当前容量的乘积时,就会触发扩容操作。
下面是HashMap的扩容机制的原理:
- 当HashMap中的元素数量超过负载因子与当前容量的乘积时,即
size > loadFactor * capacity
,就会触发扩容操作。 - 扩容操作会创建一个新的容量更大的数组,通常是当前容量的两倍。
- 遍历原数组中的所有元素,重新计算每个键值对的哈希码,然后根据新容量的大小计算新的数组索引。
- 将每个键值对存储到新的数组中的对应位置,形成新的HashMap。
- 扩容完成后,原来的数组会被丢弃,成为可回收的垃圾。
扩容的目的是为了减少哈希冲突,保持数组中的链表或红黑树的长度较短,提高查找、插入和删除操作的性能。通过扩大容量,可以增加数组的槽位数量,使得键值对能够更均匀地分布在数组中,减少碰撞的概率。
需要注意的是,扩容操作可能会引起大量的键值对重新哈希和移动,这会耗费一定的时间和计算资源。因此,在设计HashMap时,需要根据实际情况合理设置负载因子,以在空间利用率和性能之间做出权衡。
ConcurrentHashMap和HashMap是什么关系,有什么区别?
ConcurrentHashMap和HashMap是Java集合框架中的两个不同的实现,它们有一些相似之处,但也存在一些关键的区别。
- 线程安全性:
- HashMap是非线程安全的,不适合在多线程环境下并发访问和修改。如果多个线程同时对HashMap进行修改,可能会导致数据不一致或抛出异常。
- ConcurrentHashMap是线程安全的,它通过使用分段锁(Segment Level Locking)和CAS操作(Compare and Swap)等技术来实现高效的并发访问和修改。多个线程可以同时对ConcurrentHashMap进行读取和写入操作,而不会导致数据不一致。
- 性能和并发度:
- 在单线程环境下,HashMap的性能可能更好,因为它不需要额外的并发控制开销。
- 在多线程环境下,并发度较高的情况下,ConcurrentHashMap的性能通常会优于HashMap,因为它可以支持更高的并发度,通过细粒度的锁机制和CAS操作减少了锁的争用。
- 迭代器:
- HashMap的迭代器在遍历期间不是强一致性的。如果在迭代期间对HashMap进行修改,可能会导致ConcurrentModificationException异常。
- ConcurrentHashMap的迭代器是强一致性的。它可以在遍历期间检测到并发修改,并防止抛出ConcurrentModificationException异常。
综上所述,ConcurrentHashMap是对HashMap的并发安全版本,它提供了线程安全的操作,并优化了在多线程环境下的性能和并发度。因此,如果需要在多线程环境中安全地进行并发操作,应选择ConcurrentHashMap;如果在单线程环境下或者不需要并发安全的场景下,可以选择HashMap以获得更好的性能。
ConcurrentHashMap和HashTable有什么区别?
ConcurrentHashMap和HashTable是Java集合框架中的两个线程安全的哈希表实现,它们有以下区别:
- 锁的粒度:
- ConcurrentHashMap使用了分段锁(Segment Level Locking)的机制,将哈希表分成多个段,每个段都拥有独立的锁。这样可以使多个线程在并发访问时可以同时操作不同的段,减少了锁的竞争,提高了并发性能。
- HashTable使用了全局锁(Synchronized)来保护整个哈希表,即一次只能有一个线程对整个表进行操作。这导致在并发访问时只能一个线程执行操作,性能较差。
- 空键和空值:
- ConcurrentHashMap允许使用null作为键和值。它对null值进行了特殊处理,不会抛出NullPointerException异常。
- HashTable不允许使用null作为键或值,如果尝试将null值存储到HashTable中,会抛出NullPointerException异常。
- 迭代器:
- ConcurrentHashMap的迭代器是弱一致性的(weakly consistent),它不会抛出ConcurrentModificationException异常。迭代器在遍历期间可以反映出迭代开始时的状态,但无法保证在遍历期间其他线程对Map的修改是否可见。
- HashTable的迭代器是强一致性的(fail-fast),如果在迭代期间对HashTable进行修改,会抛出ConcurrentModificationException异常。
- 继承关系:
- ConcurrentHashMap是JDK 1.5引入的新类,实现了ConcurrentMap接口,它是HashMap的并发安全版本。
- HashTable是早期Java版本中提供的线程安全的哈希表实现,它继承自Dictionary类。
总体而言,ConcurrentHashMap是Java集合框架中更现代和高效的线程安全哈希表实现,相比之下,HashTable的性能和并发性能较差。因此,在并发环境下,推荐使用ConcurrentHashMap,而HashTable已经逐渐被废弃。