1.说说Java中常用的容器有哪些容器主要包括Collection和Map两种Collection 存储着对象的集合 而 Map 存储着键值对(两个对象)的映射表。如图‍面试官追问说说集合有哪些类及他们各自的区别和特点SetTreeSet基于红黑树实现支持有序性操作例如根据一个范围查找元素的操作。但是查找效率不如 HashSetHashSet 查找的时间复杂度为 O(1)TreeSet 则为 O(logN)。HashSet基于HashMap实现支持快速查找但不支持有序性操作。并且失去了元素的插入顺序信息也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。LinkedHashSet是 HashSet 的子类并且其内部是通过 LinkedHashMap 来实现的。内部使用双向链表维护元素的插入顺序。ListArrayList基于动态数组实现支持随机访问。Vector和 ArrayList 类似但它是线程安全的。LinkedList基于双向链表实现只能顺序访问但是可以快速地在链表中间插入和删除元素。不仅如此LinkedList 还可以用作栈、队列和双向队列。QueueLinkedList可以用它来实现双向队列。PriorityQueue基于堆结构实现可以用它来实现优先队列。ArrayQueue基于数组实现可以用它实现双端队列也可以作为栈。‍面试官追问说说Map有哪些类及他们各自的区别和特点TreeMap基于红黑树实现。HashMap1.7基于数组链表实现1.8基于数组链表红黑树。链表则是主要为了解决哈希冲突而存在的“拉链法”解决冲突。JDK1.8 以后在解决哈希冲突时有了较大的变化当链表长度大于阈值默认为 8将链表转换成红黑树前会判断如果当前数组的长度小于 64那么会选择先进行数组扩容而不是转换为红黑树时将链表转化为红黑树以减少搜索时间。HashTable和 HashMap 类似但它是线程安全的这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。它是遗留类不应该去使用它。现在可以使用 ConcurrentHashMap 来支持线程安全并且 ConcurrentHashMap 的效率会更高(1.7 ConcurrentHashMap 引入了分段锁, 1.8 引入了红黑树)。LinkedHashMap继承自 HashMap。使用双向链表来维护元素的顺序顺序为插入顺序或者最近最少使用(LRU)顺序。2.详细说说 Arraylist 和 LinkedList的区别?ArrayList底层是基于数组实现的查找快增删较慢。LinkedList不支持高效的随机元素访问而ArrayList支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。LinkedList底层是基于链表实现的。确切的说是循环双向链表(JDK1.6之前是双向循环链表、JDK1.7之后取消了循环)查找慢、增删快。LinkedList链表由一系列表项连接而成一个表项包含3个部分︰元素内容、前驱表和后驱表。因此内存空间占用比ArrayList 更多。‍面试官追问ArrayList的增删一定比LinkedList要慢吗不一定的。如果增删都是在末尾来操作每次调用的都是remove()和add()此时 ArrayList就不需要移动和复制数组来进行操作了。如果数据量有百万级的时速度是会比 LinkedList 要快的。如果删除操作的位置是在中间。由于LinkedList的消耗主要是在遍历上ArrayList的消耗主要是在移动和复制上(底层调用的是arrayCopy()方法是native方法)。LinkedList 的遍历速度是要慢于ArrayList的复制移动速度的。如果数据量有百万级的时还是ArrayList要快。3.ArrayList实现 RandomAccess接口有何作用public interface RandomAccess {}查看源码我们发现实际上RandomAccess接口中什么都没有定义。从源码可以看出RandomAccess 接口只是一个标志接口只要List集合实现这个接口就能支持快速随机访问。通过查看Collections类中的binarySearch()方法可以看出判断List是否实现RandomAccess接口来实行indexedBinarySerach(list, key)或iteratorBinarySerach(list, key)方法。再通过查看这两个方法的源码发现实现RandomAccess接口的List集合采用一般的 for循环遍历而未实现这接口则采用迭代器即ArrayList 一般采用for循环遍历而 LinkedList 一般采用迭代器遍历public staticint binarySearch(List? extends Comparable? super T list, T key) {if (list instanceof RandomAccess || list.size()BINARYSEARCH_THRESHOLD)return Collections.indexedBinarySearch(list, key);elsereturn Collections.iteratorBinarySearch(list, key);}篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、场景题、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】​https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho‍面试官追问为何LinkedList却没实现这个接口ArrayList底层是数组而LinkedList底层是链表。数组天然支持随机访问时间复杂度为 O(1)所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素时间复杂度为 O(n)所以不支持快速随机访问。ArrayList实现了RandomAccess接口就表明了他具有快速随机访问功能。RandomAccess接口只是标识并不是说ArrayList实现RandomAccess接口才具有快速随机访问功能的4.说一说Vector 和 ArrayList 的区别他们两个都实现了List接口。底层数据结构都是数组。不同的是vector通过remove、add等方法加上synchronized关键字实现线程同步所以是线程安全的。而ArrayList是线程不安全的由于vector使用了synchronized进行加锁所以性能不如ArrayListVector 扩容时如果未指定扩容递增值capacityIncrement或该值不大于 0 时每次扩容为原来的2倍否则扩容量为capacityIncrement的值。ArrayList每次扩容为旧容量的1.5倍5.说说ArrayList 的扩容机制当使用add方法的时候首先调用ensureCapacityInternal方法传入size1进去检查是否需要扩容如果空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA就初始化为默认大小10获取“默认的容量”和要扩容的大小两者之间的最大值和当前数组长度比较如果if (minCapacity - elementData.length 0)执行grow扩容方法将数组扩容为原来的1.5倍int newCapacity oldCapacity (oldCapacity 1);检查新容量是否大于最小需要容量若还是小于最小需要容量那么就把最小需要容量当作数组的新容量再检查新容量newCapacity 是否超出了ArrayList所定义的最大容量若超出了则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE如果minCapacity大于MAX_ARRAY_SIZE则新容量则为Interger.MAX_VALUE否则新容量大小则为 MAX_ARRAY_SIZEMAX_ARRAY_SIZE Integer.MAX_VALUE - 8ArrayList 中copy数组的核心就是System.arraycopy方法将original数组的所有数据复制到copy数组中这是一个本地方法详细的扩容源码可以参考https://blog.csdn.net/qq_45966440/article/details/122270715?spm1001.2014.3001.55016.Array和ArrayList有何区别Array可以容纳基本类型和对象而ArrayList只能容纳对象Array是指定大小的ArrayList 的容量是根据需求自动扩展ArrayList提供了更多的方法和特性比如addAll()removeAll()iterator()等等‍面试官追问什么时候更适合使用Array如果列表的大小已经指定大部分情况下是存储和遍历它们可以使用Array对于基本类型数据ArrayList 使用自动装箱来减少编码工作量而当处理固定大小的基本数据类型的时候这种方式相对比较慢这时候应该使用Array如果你要使用多维数组使用[][]比 List更容易7.遍历一个List有哪些不同的方式先说一下常见的元素在内存中的存储方式主要有两种:顺序存储(Random Access)相邻的数据元素在内存中的位置也是相邻的可以根据元素的位置读取元素。链式存储(Sequential Access)每个数据元素包含它下一个元素的内存地址在内存中不要求相邻。例如LinkedList。主要的遍历方式主要有三种for循环遍历遍历者自己在集合外部维护一个计数器依次读取每一个位置的元素Iterator遍历基于顺序存储集合的Iterator可以直接按位置访问数据。基于链式存储集合的Iterator需要保存当前遍历的位置然后根据当前位置来向前或者向后移动指针foreach遍历也就是增强for循环foreach内部也是采用了Iterator的方式实现但使用时不需要显示地声明Iterator代码如下public class TestLinkedList {public static void main(String[] args) {List list new ArrayList();list.add (“aaa”);list.add(“bbb”);list.add(“ccc”);//for循环遍历for (int i 0; i list.size(); i) {System .out.println(list.get(i));}//Iterator遍历Iterator iterator list.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}//foreach遍历for (String s : list) {System.out.println(s);}}}‍面试官追问那么对于以上三种遍历方式应该如何选取呢在Java集合框架中提供了一个RandomAccess接口该接口没有方法只是一个标记。通常用来标记List的实现是否支持RandomAccess。所以在遍历时可以先判断是否支持RandomAccess ( list instanceof RandomAccess)如果支持可用for循环遍历否则建议用Iterator或 foreach遍历。8.comparable和comparator的区别comparable接口出自java.lang包可以理解为一个内比较器因为实现了comparable接口的类可以和自己比较要和其他实现了Comparable接口类比较可以使用compareTo(objectobj)方法。compareTo方法的返回值是int有三种情况返回正整数比较者大于被比较者返回0比较者等于被比较者返回负整数比较者小于被比较者comparator接口出自java.util包它有一个compare(object obj1object obj2)方法用来排序返回值同样是int有三种情况和compareTo类似。它们之间的区别很多包装类都实现了comparable接口像Integer、string等。所以直接调用co1lections.sort()直接可以使用。如果对类里面自带的自然排序不满意而又不能修改其源代码的情况下使用comparator就比较合适。此外使用comparator可以避免添加额外的代码与我们的目标类耦合同时可以定义多种排序规则这一点是comparable接口没法做到的从灵活性和扩展性讲Comparator更优故在面对自定义排序的需求时可以优先考虑使用comparator接口。9.Collection和Collections有什么区别Collection是最基本的集合接口它提供了对集合对象进行基本操作的通用接口方法。一个Collection代表一组Object即Collection的元素。它的直接继承接口有ListSet 和Queue。Collections是不属于Java的集合框架的它是集合类的一个工具类。此类不能被实例化服务于Java的Collection框架。它包含有关集合操作的静态多态方法实现对各种集合的搜索、排序、线程安全等操作。10.说一下PriorityQueue篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、场景题、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】​https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1hoPriorityQueue是在 JDK1.5 中被引入的, 其与Queue的区别在于元素出队顺序是与优先级相关的即总是优先级最高的元素先出队。它有这些特点PriorityQueue利用了二叉堆的数据结构来实现的底层使用可变长的数组来存储数据。PriorityQueue通过堆元素的上浮和下沉实现了在O(logn)的时间复杂度内插入元素和删除堆顶元素。PriorityQueue是非线程安全的且不支持存储NULL和non-comparable的对象。PriorityQueue默认是小顶堆但可以接收一个Comparator作为构造参数从而来自定义元素优先级的先后。默认容量是11。当数组比较小小于64的时候每次扩容容量翻倍。当数组比较大大于等于64的时候每次扩容只增加一半的容量。PriorityQueue不是有序的只有堆顶存储着最小的元素可以参考PriorityQueue源码https://blog.csdn.net/qq_45966440/article/details/122273598?spm1001.2014.3001.550111.说一下HashSet的实现原理HashSet的实现是依赖于HashMap的HashSet 的值都是存储在HashMap中的。在 HashSet 的构造法中会初始化一个HashMap对象HashSet 不允许值重复。因此HashSet的值是作为HashMap的key存储在HashMap 中的当存储的值已经存在时返回false。‍面试官追问HashSet有哪些特点无序性存储元素无序唯一性允许使用null本质上HashSet的值是作为HashMap的key存储在HashMap 中的因此保证唯一性HashSet没有提供get()方法同HashMap一样因为Set内部是无序的所以只能通过迭代的方式获得12.HashMap的实现原理/底层数据结构JDK1.7数组 链表JDK1.8数组 链表 | 红黑树HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值然后通过 (n - 1) hash 判断当前元素存放的位置这里的 n 指的是数组的长度如果当前位置存在元素的话就判断该元素与要存入的元素的 hash 值以及 key 是否相同如果相同的话直接覆盖不相同就通过拉链法解决冲突。所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。JDK1.8的hash方法static final int hash(Object key) {int h;// key.hashCode()返回散列值也就是hashcode// ^ 按位异或// :无符号右移忽略符号位空位都以0补齐return (key null) ? 0 : (h key.hashCode()) ^ (h 16);}JDK1.7的hash方法static int hash(int h) {h ^ (h 20) ^ (h 12);return h ^ (h 7) ^ (h 4);}从源码可以看出JDK 1.8 的 hash 方法 相比于 JDK 1.7 hash 方法更加简化但是原理不变。JDK 1.7 的 hash 方法的性能会稍差一点点因为毕竟扰动了 4 次。