集合框架,以及List、Set和Map的关系、是否线程安全、底层数据结构
集合框架是一组接口和类,用于存储对象,并提供一些便利的集合操作。分为Collection
和Map
两大类。
-
Collection
:是集合框架的根接口,它定义了一些基本的操作,如添加元素、删除元素、遍历集合等。-
List
:继承自Collection
接口,代表有序的集合,允许存储重复的元素。 -
Set
:继承自Collection
接口,代表不允许存储重复元素的集合。 -
Queue
:队列,按照特定的顺序添加和删除元素。
-
-
Map
:是一种键值对存储结构,用于将键映射到相应的值。键不重复。
线程安全
-
List、Set 和 Map 的具体实现类在线程安全方面有所不同。
-
一些常见的线程安全实现类包括
Vector
、HashTable
等。 -
而大部分常用的非线程安全实现类有
ArrayList
、LinkedList
、HashSet
、TreeSet
、HashMap
、TreeMap
等。
如果在多线程环境下需要使用线程安全的集合,可以选择使用相应的线程安全类,或者通过同步机制来保证线程安全。
底层数据结构:
-
List 的常见底层数据结构包括数组和链表。例如,
ArrayList
使用动态数组实现,LinkedList
使用双向链表实现。 -
Set 的底层数据结构通常是哈希表或红黑树。例如,
HashSet
使用哈希表实现,TreeSet
使用红黑树实现。 -
Map 的底层数据结构也有多种选择,常见的有哈希表和红黑树。例如,
HashMap
使用哈希表实现,TreeMap
使用红黑树实现。
Vector和ArrayList的底层实现,区别和使用场景
底层实现都是基于数组
Vector:Vector 是基于数组实现的动态数组。它内部使用一个对象数组来存储元素,并支持自动扩容。当元素数量超过当前数组的容量时,Vector 会创建一个更大的数组,并将原数组中的元素复制到新数组中。
ArrayList:ArrayList 也是基于数组实现的动态数组。它与 Vector 的底层实现类似,同样使用对象数组来存储元素,并支持自动扩容。
Vector
和 ArrayList
的主要区别如下:
-
线程安全:
Vector
是线程安全的,而ArrayList
不是。 -
性能:由于线程安全的开销,
Vector
的性能可能会比ArrayList
略低。 -
扩容机制:当数组容量不足时,
Vector
和ArrayList
都会进行扩容操作。但是,Vector
的扩容机制是在创建对象时指定的,而ArrayList
的扩容机制是在需要扩容时自动扩容的。 -
迭代器:
Vector
和ArrayList
都提供了迭代器,但是Vector
的迭代器是在创建对象时指定的,而ArrayList
的迭代器是在需要迭代时自动创建的。
Vector
和 ArrayList
的使用场景如下:
-
如果需要在多线程环境中使用动态数组,并且不需要进行额外的同步操作,那么可以使用
Vector
。 -
如果需要在单线程环境中使用动态数组,并且对性能要求较高,那么可以使用
ArrayList
。 -
如果需要在动态数组中存储大量的对象,并且对性能要求较高,那么可以使用
ArrayList
。
ArrayList和LinkedList区别
-
数据结构
-
ArrayList:动态数组,连续内存空间
-
LinkedList:双向链表,节点之间的链接
-
-
内存占用:
-
ArrayList:高,需预分配连续内存空间
-
LinkedList:低,只需要给每个节点分配内存
-
-
随机访问:
-
ArrayList:快,直接索引,时间复杂度O(1)
-
LinkedList:慢,需从头开始遍历,时间复杂度O(n)
-
-
插入删除:
-
ArrayList:慢,需重新创建数组,然后遍历赋值。时间复杂度O(n)
-
LinkedList:快,插入的对象的左右对象,更新位置关系即可,时间复杂度O(1)
-
数组和链表的使用场景及优缺点
一、数组
-
使用场景:
-
频繁访问元素:数组可以通过索引快速访问元素。
-
存储固定大小的数据:数组的长度在创建时就已经确定。
-
数据类型相同:数组中的元素类型必须相同。
-
-
优点:
-
快速且随机访问元素:索引访问,时间复杂度为 O(1)。
-
存储效率高:元素在内存中是连续存储的,存储效率高。
-
-
缺点:
-
插入和删除元素效率低:需要移动其他元素,时间复杂度为 O(n)。
-
数组长度固定:不能动态扩展或缩小。
-
存储空间浪费:元素在内存中是连续存储的,数组长度较大,浪费存储空间。
-
二、链表
-
使用场景:
-
频繁插入和删除元素:不需要移动其他元素。
-
存储不固定大小的数据:链表的长度可以动态扩展或缩小。
-
数据类型不同:链表中的元素类型可以不同。
-
-
优点:
-
插入和删除元素效率高:时不需要移动其他元素,时间复杂度为 O(1)。
-
链表长度灵活:可以动态扩展或缩小,不需要预先分配存储空间。
-
存储空间利用率高:元素在内存中不是连续存储的,可以充分利用存储空间。
-
-
缺点:
-
访问元素效率低:需要从头节点开始遍历链表,时间复杂度为 O(n)。
-
存储效率低:链表中的元素在内存中不是连续存储的,存储效率低。
-
优先级队列
优先级队列是一种特殊的队列,其中的每个元素都有一个优先级。在优先级队列中,元素按照优先级进行排序,优先级高的元素先出队。
使用多种数据结构来实现,例如堆、二叉搜索树等。其中,堆是一种常用的数据结构,它可以高效地实现优先级队列。
元素的插入操作会根据元素的优先级将元素插入到合适的位置上,保持堆的特性。
元素的删除操作会删除堆顶元素。
场景:任务调度、事件处理等。通过使用优先级队列,我们可以根据优先级来安排任务的执行顺序,或者处理具有不同优先级的事件。
Set和Map的底层实现
Set 的实现类:
-
HashSet
:基于哈希表实现。它使用哈希函数将元素映射到哈希表的桶中,以提高查找和插入的效率。HashSet
不保证元素的顺序。 -
TreeSet
:基于红黑树实现。它将元素存储在有序的红黑树中,以保持元素的有序性。TreeSet
提供了对元素的自然排序或自定义排序。 -
LinkedHashSet
:基于哈希表和链表实现。它在HashSet
的基础上,通过维护元素的插入顺序,保证了元素的迭代顺序与插入顺序一致。
Map 的实现类:
-
HashMap
:基于哈希表实现。它使用哈希函数将键映射到哈希表的桶中,以提高查找和插入的效率。HashMap
不保证键的顺序。 -
TreeMap
:基于红黑树实现。它将键存储在有序的红黑树中,以保持键的有序性。TreeMap
提供了对键的自然排序或自定义排序。 -
LinkedHashMap
:基于哈希表和链表实现。它在HashMap
的基础上,通过维护键的插入顺序,保证了键的迭代顺序与插入顺序一致。
HashTable
Hashtable
:与 HashMap
类似,但它是线程安全的。在多线程环境下,可以使用 Hashtable
来保证线程安全。
HashMap和HashTable区别
table线程安全、map允许空值、map性能好、table迭代是快速失败、table无法指定初始容量和负载因子
New HashSet、HashMap,初始的哈希表容量:16
HashTable是11
哈希表
通常使用数组、链表、红黑树来实现。
用“哈希函数”将“键key”转化为哈希值,映射到数组的特定位置(桶,存储kv。
可以实现快速的查找、插入和删除操作。时间复杂度接近O(1)。
-
哈希函数:哈希表使用一个哈希函数将键转换为一个整数哈希值。哈希函数的目的是将不同的键映射到数组的不同位置,以尽量减少冲突。
-
数组:创建一个固定大小的数组,用于存储键值对。数组的每个元素可以是一个链表节点或其他数据结构。
-
链表:当发生哈希冲突时,即多个键映射到数组的同一个位置时,使用链表来存储冲突的键值对。每个链表节点包含键、值和指向下一个节点的指针。
-
插入操作:当插入一个键值对时,首先计算键的哈希值,然后将其存储在数组的相应位置。如果该位置已经存在其他键值对(发生冲突),则将新的键值对添加到链表的末尾。
-
查找操作:查找一个键值对时,同样计算键的哈希值,然后在数组的相应位置查找。如果找到匹配的键,则返回对应的值;如果该位置存在链表,则遍历链表查找匹配的键。
-
删除操作:删除一个键值对时,计算键的哈希值,找到相应的位置和链表。在链表中找到匹配的键后,将其从链表中删除。
当哈希表中的元素数量增加时,可能会出现更多的哈希冲突,导致链表变长,从而影响性能。
为了减少冲突,可以选择合适的哈希函数、调整数组大小或采用其他优化技术。
哈希冲突
解决哈希冲突的常见方法有
-
开放寻址法:在哈希表中找空闲位置
-
链表法:每个哈希桶都链接一个链表
-
红黑树法:当链表的长度超过一定阈值(默认是 8)时,
HashMap
会将链表转换为红黑树。红黑树是一种平衡二叉搜索树,它可以提高查询的效率,特别是在链表较长的情况下。
-
-
扩容:当哈希表的负载因子超过一定阈值(默认是 0.75)时,
HashMap
会进行扩容操作,增加哈希表的容量。扩容后,原来的元素会重新计算哈希值,并重新分布到新的哈希桶中,从而减少哈希冲突的发生。负载因子:已存储的元素数量 / 哈希表容量 -
再哈希:使用不同的哈希函数,再次哈希
红黑树
自平衡的二叉搜索树,时间复杂度为O(log n)
二叉搜索树
二叉搜索树(Binary Search Tree)是一种特殊的二叉树,它具有以下特点:
-
左子树上所有节点的值均小于根节点的值。
-
右子树上所有节点的值均大于根节点的值。
-
左子树和右子树也都是二叉搜索树。
优点是查找、插入和删除操作的时间复杂度均为 O(log n),其中 n 是树中的节点数量。但是,如果二叉搜索树退化成了链表(即所有节点只有左子树或右子树),则这些操作的时间复杂度会退化为 O(n)
Btree
平衡的多路搜索树,它的每个节点可以有多个子节点。BTree 通常用于数据库和文件系统等需要高效存储和检索大量数据的场景。
列举线程安全容器,解释如何保证线程安全
-
Vector
:动态数组。synchronized
关键字同步方法。 -
HashTable
:哈希表。synchronized
关键字同步方法。 -
ConcurrentHashMap
:高效哈希表。分段锁技术,数据分多段分别加锁。 -
CopyOnWriteArrayList
:可变数组。写入时会复制整个数组。 -
CopyOnWriteArraySet
:集合,内部使用CopyOnWriteArrayList
来实现。 -
ConcurrentLinkedQueue
:基于链表的无界队列。采用非阻塞算法和高并发数据结构。无锁链表、原子操作 -
BlockingQueue
:阻塞式队列。队列为空或已满时,线程可以被阻塞等待。常用于生产者-消费者模式中,生产者线程可以将元素放入队列,而消费者线程从队列取元素。常见实现类ArrayBlockingQueue
、LinkedBlockingQueue
等。
线程安全
-
同步方法:容器的方法使用
synchronized
关键字进行同步,确保在同一时间只有一个线程可以访问这些方法。 -
锁机制:容器内部使用锁来保护共享数据结构,以确保线程安全。
-
数据复制:在某些情况下,容器会在写入时复制数据,以避免并发冲突。
-
并发控制工具:一些容器可能使用更高级的并发控制工具,如
ReentrantLock
或ReadWriteLock
,来提供更细粒度的并发控制。