Java面试 - Java集合

本文详细介绍了Java集合框架中的List、Set和Map的区别。List是有序且可重复的,如ArrayList和LinkedList,其中ArrayList适合随机访问,而LinkedList适合频繁插入删除。Set是无序且不可重复的,包括HashSet和TreeSet等。Map是一对一的双列集合,如HashMap,提供Key-Value存储,HashMap在冲突时使用链表或红黑树解决。此外,还讨论了它们的底层实现、时间复杂度和并发安全性,如Vector和Hashtable的线程安全特性以及ConcurrentHashMap的并发控制策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java集合框架

https://juejin.cn/post/6845166891397300238#heading-13

在这里插入图片描述

说一说 List Set Map的区别

List 存储的元素是有序的,是可以重复的
Set 无序的,不可重复的
Map 一对一对的,双列集合,key是无序的,不可重复的,value是无序的,可重复的

List

存储有序的,可重复的数据,动态数组,替换原来的数组
https://juejin.cn/post/6845166891397300238

  • ArrayList 线程不安全,底层是Object数组
  • LinkedList 线程不安全,底层是双向链表
  • Vector 线程安全,底层也是Object数组

ArrayList 底层原理

new 一个arrayList时,底层数组初始化为{},当第一次add的时候才创建了长度为10的数组,并将数据添加到数组中,当数组容量不够时,就扩容,创建了一个长度为原来的1.5倍的新数组,并将原数组的数据复制到新数组中

ArrayList和LinkedList的时间复杂度的比较

add(E e) 都是在末尾添加元素,时间复制度都是O(1)

//对于LinkedList的源码,保存了LinkedList的first和last属性
transient Node<E> first;
transient Node<E> last;
private static class Node<E> {
   E item;
   Node<E> next;
   Node<E> prev;
   Node(Node<E> prev, E element, Node<E> next) {
       this.item = element;
       this.next = next;
       this.prev = prev;
   }
}

add(int index,E e) 在指定位置添加元素
arraylist 找到这个元素的时间复杂度是O(1) 但是它添加元素的话,要将这之后的元素后移,时间复制度是O(n)
LinkedList 添加元素的时间复杂度是O(1) 但是找到一个元素的时间复杂度是O(n)

remove(int index)
ArrayList 找到这个元素的过程是 O(1),但是 remove 之后,后续元素都要往前移动一位,所以均摊复杂度是 O(n);
LinkedList 也是要先找到这个 index,这个过程是 O(n) 的,所以整体也是 O(n)。

改和查

因为数组支持随机访问,所以ArrayList的效率高

Vector和ArrayList的区别

首先是Vector是线程安全的,其次时Vector扩容时是扩容为原来的两倍

Queue 队列

队头出,队尾进的一种数据结构,可分为普通队列,双向队列Deque,优先队列PriorityQueue

在这里插入图片描述
比如队列空了,那 remove() 就会抛异常,但是 poll() 就返回 null;element() 就会抛异常,而 peek() 就返回 null 就好了。

add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
offer 添加一个元素并返回true 如果队列已满,则返回false
poll 移除并返问队列头部的元素 如果队列为空,则返回null
peek 返回队列头部的元素 如果队列为空,则返回null
put 添加一个元素 如果队列满,则阻塞
take 移除并返回队列头部的元素 如果队列为空,则阻塞

Set

无序的,不重复的
在这里插入图片描述
HashSet 采用HashMap的key来存储元素
LinedHashSet 这是一个HashSet+LinkedList的结构,特点就是既拥有O(1)的时间复杂度,又能够保留插入的顺序
TreeSet 采用红黑树结构,可以有序,可以用自然排序或者自定义比较器来排序

Map

HashMap(重点)

底层数据结构是数组(哈希桶数组)+链表+红黑树

首先new 一个hashmap的时候,并没有创建一个长度为16的数组,首次调用put方法的时候才创建的,调用key所在类的hashcode去计算哈希值,得到在数组中的存放位置,如果位置上有数据的话,就比较hash值,如果hash值相同的就通过key所在类的equals进行比较,如果相同就插入失败。发送hash冲突,通过链地址法解决,当链的长度大于8时,就将链表转换成红黑树,之后链的长度小于6时,又退化为链表
在这里插入图片描述

数组里面存储的是 Node对象

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;    //用来定位数组索引位置
        final K key;
        V value;
        Node<K,V> next;   //链表的下一个node

        Node(int hash, K key, V value, Node<K,V> next) { ... }
        public final K getKey(){ ... }
        public final V getValue() { ... }
        public final String toString() { ... }
        public final int hashCode() { ... }
        public final V setValue(V newValue) { ... }
        public final boolean equals(Object o) { ... }
}

扩容机制

当超过临界值时,扩容,而不是满了再扩容,默认是将长度扩容为原来的2倍,并将原来的数组重新计算hash值,插入到新数组
扩容的临界值 = 容量*填充因子 : 16 * 0.75 = 12
在这里插入图片描述

Hashtable

就是线程安全的hashmap,当加锁的时候,对整个hashmap进行加锁

HashMap和HashTable的区别

HashMap是线程不安全的,HashTable是线程安全的
HashMap允许key和value为null,而hashtable不允许为null

这是因为Hashtable使用的是安全失败机制(fail-safe),这种机制会使你此次读到的数据不一定是最新的数据。
如果你使用null值,就会使得其无法判断对应的key是不存在还是为空,因为你无法再调用一次contain(key)来对key是否存在进行判断,ConcurrentHashMap同理。

初始容量大小和每次扩容的大小不同:hashtable的初始大小为11,之后每次扩容为原来的2n+1,,负载因子都是0.75

ConcurrentHashMap

直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用synchronized和cas来操作
跟HashMap很像,也把之前的HashEntry改成了Node,但是作用不变,把值和next采用了volatile去修饰,保证了可见性

put操作是如何保证线程安全的?

  1. 根据key计算出hashcode
  2. 判断是否需要进行初始化
  3. 找到当前key定位出的node,如果为空表示当前位置可以写入数据,利用cas尝试写入,失败则自旋保证成功
  4. 如果当前位置的hashcode == moved == -1 则需要扩容
  5. 如果都不满足,则利用synchronzied锁写入数据
    在这里插入图片描述
    CAS
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值