Java集合面试

集合框架,以及List、Set和Map的关系、是否线程安全、底层数据结构

集合框架是一组接口和类,用于存储对象,并提供一些便利的集合操作。分为CollectionMap两大类。

  • Collection:是集合框架的根接口,它定义了一些基本的操作,如添加元素、删除元素、遍历集合等。

    • List:继承自 Collection 接口,代表有序的集合,允许存储重复的元素。

    • Set:继承自 Collection 接口,代表不允许存储重复元素的集合。

    • Queue :队列,按照特定的顺序添加和删除元素。

  • Map:是一种键值对存储结构,用于将键映射到相应的值。键不重复。

线程安全

  • List、Set 和 Map 的具体实现类在线程安全方面有所不同。

  • 一些常见的线程安全实现类包括 VectorHashTable 等。

  • 而大部分常用的非线程安全实现类有 ArrayListLinkedListHashSetTreeSetHashMapTreeMap 等。

如果在多线程环境下需要使用线程安全的集合,可以选择使用相应的线程安全类,或者通过同步机制来保证线程安全。

底层数据结构:

  • List 的常见底层数据结构包括数组和链表。例如,ArrayList 使用动态数组实现,LinkedList 使用双向链表实现。

  • Set 的底层数据结构通常是哈希表或红黑树。例如,HashSet 使用哈希表实现,TreeSet 使用红黑树实现。

  • Map 的底层数据结构也有多种选择,常见的有哈希表和红黑树。例如,HashMap 使用哈希表实现,TreeMap使用红黑树实现。

Vector和ArrayList的底层实现,区别和使用场景

底层实现都是基于数组

Vector:Vector 是基于数组实现的动态数组。它内部使用一个对象数组来存储元素,并支持自动扩容。当元素数量超过当前数组的容量时,Vector 会创建一个更大的数组,并将原数组中的元素复制到新数组中。

ArrayList:ArrayList 也是基于数组实现的动态数组。它与 Vector 的底层实现类似,同样使用对象数组来存储元素,并支持自动扩容。

Vector 和 ArrayList 的主要区别如下:

  1. 线程安全:Vector 是线程安全的,而 ArrayList 不是。

  2. 性能:由于线程安全的开销,Vector 的性能可能会比 ArrayList 略低。

  3. 扩容机制:当数组容量不足时,Vector 和 ArrayList 都会进行扩容操作。但是,Vector 的扩容机制是在创建对象时指定的,而 ArrayList 的扩容机制是在需要扩容时自动扩容的。

  4. 迭代器:Vector 和 ArrayList 都提供了迭代器,但是 Vector 的迭代器是在创建对象时指定的,而 ArrayList 的迭代器是在需要迭代时自动创建的。

Vector 和 ArrayList 的使用场景如下:

  1. 如果需要在多线程环境中使用动态数组,并且不需要进行额外的同步操作,那么可以使用 Vector

  2. 如果需要在单线程环境中使用动态数组,并且对性能要求较高,那么可以使用 ArrayList

  3. 如果需要在动态数组中存储大量的对象,并且对性能要求较高,那么可以使用 ArrayList

ArrayList和LinkedList区别

  • 数据结构

    • ArrayList:动态数组,连续内存空间

    • LinkedList:双向链表,节点之间的链接

  • 内存占用:

    • ArrayList:高,需预分配连续内存空间

    • LinkedList:低,只需要给每个节点分配内存

  • 随机访问:

    • ArrayList:快,直接索引,时间复杂度O(1)

    • LinkedList:慢,需从头开始遍历,时间复杂度O(n)

  • 插入删除:

    • ArrayList:慢,需重新创建数组,然后遍历赋值。时间复杂度O(n)

    • LinkedList:快,插入的对象的左右对象,更新位置关系即可,时间复杂度O(1)

数组和链表的使用场景及优缺点

一、数组

  1. 使用场景:

    1. 频繁访问元素:数组可以通过索引快速访问元素。

    2. 存储固定大小的数据:数组的长度在创建时就已经确定。

    3. 数据类型相同:数组中的元素类型必须相同。

  2. 优点:

    1. 快速且随机访问元素:索引访问,时间复杂度为 O(1)。

    2. 存储效率高:元素在内存中是连续存储的,存储效率高。

  3. 缺点:

    1. 插入和删除元素效率低:需要移动其他元素,时间复杂度为 O(n)。

    2. 数组长度固定:不能动态扩展或缩小。

    3. 存储空间浪费:元素在内存中是连续存储的,数组长度较大,浪费存储空间。

二、链表

  1. 使用场景:

    1. 频繁插入和删除元素:不需要移动其他元素。

    2. 存储不固定大小的数据:链表的长度可以动态扩展或缩小。

    3. 数据类型不同:链表中的元素类型可以不同。

  2. 优点:

    1. 插入和删除元素效率高:时不需要移动其他元素,时间复杂度为 O(1)。

    2. 链表长度灵活:可以动态扩展或缩小,不需要预先分配存储空间。

    3. 存储空间利用率高:元素在内存中不是连续存储的,可以充分利用存储空间。

  3. 缺点:

    1. 访问元素效率低:需要从头节点开始遍历链表,时间复杂度为 O(n)。

    2. 存储效率低:链表中的元素在内存中不是连续存储的,存储效率低。

优先级队列

优先级队列是一种特殊的队列,其中的每个元素都有一个优先级。在优先级队列中,元素按照优先级进行排序,优先级高的元素先出队。

使用多种数据结构来实现,例如堆、二叉搜索树等。其中,堆是一种常用的数据结构,它可以高效地实现优先级队列。

元素的插入操作会根据元素的优先级将元素插入到合适的位置上,保持堆的特性。

元素的删除操作会删除堆顶元素。

场景:任务调度、事件处理等。通过使用优先级队列,我们可以根据优先级来安排任务的执行顺序,或者处理具有不同优先级的事件。

Set和Map的底层实现

Set 的实现类:

  1. HashSet:基于哈希表实现。它使用哈希函数将元素映射到哈希表的桶中,以提高查找和插入的效率。HashSet 不保证元素的顺序。

  2. TreeSet:基于红黑树实现。它将元素存储在有序的红黑树中,以保持元素的有序性。TreeSet 提供了对元素的自然排序或自定义排序。

  3. LinkedHashSet:基于哈希表和链表实现。它在 HashSet 的基础上,通过维护元素的插入顺序,保证了元素的迭代顺序与插入顺序一致。

Map 的实现类:

  1. HashMap:基于哈希表实现。它使用哈希函数将键映射到哈希表的桶中,以提高查找和插入的效率。HashMap 不保证键的顺序。

  2. TreeMap:基于红黑树实现。它将键存储在有序的红黑树中,以保持键的有序性。TreeMap 提供了对键的自然排序或自定义排序。

  3. LinkedHashMap:基于哈希表和链表实现。它在 HashMap 的基础上,通过维护键的插入顺序,保证了键的迭代顺序与插入顺序一致。

HashTable

Hashtable:与 HashMap 类似,但它是线程安全的。在多线程环境下,可以使用 Hashtable 来保证线程安全。

HashMap和HashTable区别

table线程安全、map允许空值、map性能好、table迭代是快速失败、table无法指定初始容量和负载因子

New HashSet、HashMap,初始的哈希表容量:16

HashTable是11

哈希表

通常使用数组、链表、红黑树来实现。

用“哈希函数”将“键key”转化为哈希值,映射到数组的特定位置(桶,存储kv。

可以实现快速的查找、插入和删除操作。时间复杂度接近O(1)。

  1. 哈希函数:哈希表使用一个哈希函数将键转换为一个整数哈希值。哈希函数的目的是将不同的键映射到数组的不同位置,以尽量减少冲突。

  2. 数组:创建一个固定大小的数组,用于存储键值对。数组的每个元素可以是一个链表节点或其他数据结构。

  3. 链表:当发生哈希冲突时,即多个键映射到数组的同一个位置时,使用链表来存储冲突的键值对。每个链表节点包含键、值和指向下一个节点的指针。

  4. 插入操作:当插入一个键值对时,首先计算键的哈希值,然后将其存储在数组的相应位置。如果该位置已经存在其他键值对(发生冲突),则将新的键值对添加到链表的末尾。

  5. 查找操作:查找一个键值对时,同样计算键的哈希值,然后在数组的相应位置查找。如果找到匹配的键,则返回对应的值;如果该位置存在链表,则遍历链表查找匹配的键。

  6. 删除操作:删除一个键值对时,计算键的哈希值,找到相应的位置和链表。在链表中找到匹配的键后,将其从链表中删除。

当哈希表中的元素数量增加时,可能会出现更多的哈希冲突,导致链表变长,从而影响性能。

为了减少冲突,可以选择合适的哈希函数、调整数组大小或采用其他优化技术。

哈希冲突

解决哈希冲突的常见方法有

  • 开放寻址法:在哈希表中找空闲位置

  • 链表法:每个哈希桶都链接一个链表

    • 红黑树法:当链表的长度超过一定阈值(默认是 8)时,HashMap 会将链表转换为红黑树。红黑树是一种平衡二叉搜索树,它可以提高查询的效率,特别是在链表较长的情况下。

  • 扩容:当哈希表的负载因子超过一定阈值(默认是 0.75)时,HashMap 会进行扩容操作,增加哈希表的容量。扩容后,原来的元素会重新计算哈希值,并重新分布到新的哈希桶中,从而减少哈希冲突的发生。负载因子:已存储的元素数量 / 哈希表容量

  • 再哈希:使用不同的哈希函数,再次哈希

红黑树

自平衡的二叉搜索树,时间复杂度为O(log n)

二叉搜索树

二叉搜索树(Binary Search Tree)是一种特殊的二叉树,它具有以下特点:

  1. 左子树上所有节点的值均小于根节点的值。

  2. 右子树上所有节点的值均大于根节点的值。

  3. 左子树和右子树也都是二叉搜索树。

优点是查找、插入和删除操作的时间复杂度均为 O(log n),其中 n 是树中的节点数量。但是,如果二叉搜索树退化成了链表(即所有节点只有左子树或右子树),则这些操作的时间复杂度会退化为 O(n)

Btree

平衡的多路搜索树,它的每个节点可以有多个子节点。BTree 通常用于数据库和文件系统等需要高效存储和检索大量数据的场景。

列举线程安全容器,解释如何保证线程安全

  • Vector:动态数组。synchronized关键字同步方法

  • HashTable:哈希表。synchronized关键字同步方法

  • ConcurrentHashMap:高效哈希表。分段锁技术,数据分多段分别加锁。

  • CopyOnWriteArrayList:可变数组。写入时会复制整个数组。

  • CopyOnWriteArraySet:集合,内部使用CopyOnWriteArrayList来实现。

  • ConcurrentLinkedQueue :基于链表的无界队列。采用非阻塞算法和高并发数据结构。无锁链表、原子操作

  • BlockingQueue :阻塞式队列。队列为空或已满时,线程可以被阻塞等待。常用于生产者-消费者模式中,生产者线程可以将元素放入队列,而消费者线程从队列取元素。常见实现类 ArrayBlockingQueueLinkedBlockingQueue 等。

线程安全

  • 同步方法:容器的方法使用synchronized关键字进行同步,确保在同一时间只有一个线程可以访问这些方法。

  • 锁机制:容器内部使用锁来保护共享数据结构,以确保线程安全。

  • 数据复制:在某些情况下,容器会在写入时复制数据,以避免并发冲突。

  • 并发控制工具:一些容器可能使用更高级的并发控制工具,如ReentrantLockReadWriteLock,来提供更细粒度的并发控制。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值