Java集合面试题(2021最新版)

前言

立刻到今年的金三银四了,又是跳槽的好季节,准备跳槽的同窗都摩拳擦掌准备大面好几场,本次小编为你们准备了精选的 Java 集合面试题,快来查漏补缺吧。java

小编分享的这份金三银四Java后端开发面试总结包含了JavaOOP、Java集合容器、Java异常、并发编程、Java反射、Java序列化、JVM、Redis、Spring MVC、MyBatis、MySQL数据库、消息中间件MQ、Dubbo、Linux、ZooKeeper、 分布式&数据结构与算法等26个专题技术点,都是小编在各个大厂总结出来的面试真题,已经有不少粉丝靠这份PDF拿下众多大厂的offer,今天在这里总结分享给到你们!【持续更新中!】node

完整版Java面试题地址:2021最新面试题合集集锦面试

序号 专题 内容 连接
1 中间件 Java中间件面试题(2021最新版) https://blog.51cto.com/14994509/2692669
2 微服务 Java微服务面试题(2021最新版) http://www.javashuo.com/article/p-haiifnkb-vk.html
3 并发编程 Java并发编程面试题(2021最新版) http://www.javashuo.com/article/p-rirekprn-vk.html
4 Java基础 Java基础知识面试题(2021最新版) https://blog.51cto.com/blogger/success/2706687
5 Spring Boot Spring Boot面试题(2021最新版) http://www.javashuo.com/article/p-etpzmprr-vk.html
6 Redis Redis面试题(2021最新版) http://www.javashuo.com/article/p-adjrmfwe-vk.html
7 Spring MVC Spring MVC面试题(2021最新版) https://blog.51cto.com/u_14994509/2711562
8 Spring Cloud Spring Cloud面试题(2021最新版) http://www.javashuo.com/article/p-emhafwrm-vk.html
9 MySQL优化 MySQL优化面试题(2021最新版) https://blog.51cto.com/u_14994509/2716504
10 JVM JVM性能调优面试题(2021最新版) https://blog.51cto.com/u_14994509/2716514
11 Linux Linux面试题(2021最新版) https://blog.51cto.com/u_14994509/2718559
12 Mybatis Mybatis面试题(2021最新版) https://blog.51cto.com/u_14994509/2718588
13 网络编程 TCP,UDP,Socket,Http网络编程面试题(2021最新版) https://blog.51cto.com/u_14994509/2718599
14 设计模式 设计模式面试题(2021最新版) https://blog.51cto.com/u_14994509/2735448
15 大数据 大数据面试题100道(2021最新版) https://blog.51cto.com/u_14994509/2736601
16 Tomcat Tomcat面试题(2021最新版) https://blog.51cto.com/u_14994509/2739386
17 多线程 多线程面试题(2021最新版) https://blog.51cto.com/u_14994509/2739435
18 Nginx Nginx_BIO_NIO_AIO面试题(2021最新版) http://www.javashuo.com/article/p-mjxzgvyc-vm.html
19 memcache memcache面试题(2021最新版) https://blog.51cto.com/u_14994509/2743980
20 java异常 java异常面试题(2021最新版) https://blog.51cto.com/u_14994509/2746759
21 Java虚拟机 Java虚拟机面试题(2021最新版) https://blog.51cto.com/u_14994509/2747596
22 Java集合 Java集合面试题(2021最新版) 持续更新中!
23 Git经常使用命令 Git经常使用命令(2021最新版) 持续更新中!
24 Elasticsearch Elasticsearch面试题(2021最新版) 持续更新中!
25 Dubbo Dubbo面试题(2021最新版) 持续更新中!

1、集合容器概述

1. 什么是集合

  • 集合就是一个放数据的容器,准确的说是放数据对象引用的容器
  • 集合类存放的都是对象的引用,而不是对象的自己
  • 集合类型主要有3种:set(集)、list(列表)和map(映射)。

2. 集合的特色

集合的特色主要有以下两点: 算法

  • 集合用于存储对象的容器,对象是用来封装数据,对象多了也须要存储集中式管理。
  • 和数组对比对象的大小不肯定。由于集合是可变长度的。数组须要提早定义大小

3. 集合和数组的区别

  • 数组是固定长度的;集合可变长度的。
  • 数组能够存储基本数据类型,也能够存储引用数据类型;集合只能存储引用数据类型。
  • 数组存储的元素必须是同一个数据类型;集合存储的对象能够是不一样数据类型。

4. 使用集合框架的好处

  1. 容量自增加;
  2. 提供了高性能的数据结构和算法,使编码更轻松,提升了程序速度和质量;
  3. 能够方便地扩展或改写集合,提升代码复用性和可操做性。
  4. 经过使用JDK自带的集合类,能够下降代码维护和学习新API成本。

5. 经常使用的集合类有哪些?

Map接口和Collection接口是全部集合框架的父接口: 数据库

  • Collection接口的子接口包括:Set接口和List接口
  • Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
  • Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
  • List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等

6. List,Set,Map三者的区别?

Java集合面试题(2021最新版)

7. 集合框架底层数据结构

Java集合面试题(2021最新版)

8. 哪些集合类是线程安全的?

  • Vector:就比Arraylist多了个 synchronized (线程安全),由于效率较低,如今已经不太建议使用。
  • hashTable:就比hashMap多了个synchronized (线程安全),不建议使用。
  • ConcurrentHashMap:是Java5中支持高并发、高吞吐量的线程安全HashMap实现。它由Segment数组结构和HashEntry数组结构组成。Segment数组在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键-值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap相似,是一种数组和链表结构;一个Segment里包含一个HashEntry数组,每一个HashEntry是一个链表结构的元素;每一个Segment守护着一个HashEntry数组里的元 素,当对HashEntry数组的数据进行修改时,必须首先得到它对应的Segment锁。(推荐使用)

9. Java集合的快速失败机制 “fail-fast”?

Java集合面试题(2021最新版)

10. 怎么确保一个集合不能被修改?

  • 可使用 Collections. unmodififiableCollection(Collection c) 方法来建立一个只读集合,这样改变集合的任何操做都会抛出 Java. lang. UnsupportedOperationException 异常。
  • 示例代码以下:
List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());

2、Collection接口编程

List接口

11. 迭代器 Iterator 是什么?

Java集合面试题(2021最新版)

12. Iterator 怎么使用?有什么特色?

Iterator 使用代码以下: 后端

List<String> list = new ArrayList<>();
Iterator<String> it = list. iterator();
while(it. hasNext()){
String obj = it. next();
System. out. println(obj);
}

Iterator 的特色是只能单向遍历,可是更加安全,由于它能够确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModifificationException 异常。 设计模式

13. 如何边遍历边移除 Collection 中的元素?

Java集合面试题(2021最新版)

14. Iterator 和 ListIterator 有什么区别?

  • Iterator 能够遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
  • Iterator 只能单向遍历,而 ListIterator 能够双向遍历(向前/后遍历)。
  • ListIterator 实现 Iterator 接口,而后添加了一些额外的功能,好比添加一个元素、替换一个元素、获取前面或后面元素的索引位置。

15. 遍历一个 List 有哪些不一样的方式?每种方法的实现原理是什么?Java 中 List 遍历的最佳实践是什么?

Java集合面试题(2021最新版)

16. 说一下 ArrayList 的优缺点

Java集合面试题(2021最新版)

17. 如何实现数组和 List 之间的转换?

  • 数组转 List:使用 Arrays. asList(array) 进行转换。
  • List 转数组:使用 List 自带的 toArray() 方法。
  • 代码示例:
// list to array
List<String> list = new ArrayList<String>();
list.add("123");
list.add("456");
list.toArray();
// array to list
String[] array = new String[]{"123","456"};
Arrays.asList(array);

18. ArrayList 和 LinkedList 的区别是什么?

  • 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
  • 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,由于 LinkedList 是线性的数据存储方式,因此须要移动指针从前日后依次查找。
  • 增长和删除效率:在非首尾的增长和删除操做,LinkedList 要比 ArrayList 效率要高,由于ArrayList 增删操做要影响数组内的其余数据的下标。
  • 内存空间占用:LinkedList 比 ArrayList 更占内存,由于 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
  • 线程安全:ArrayList 和 LinkedList 都是不一样步的,也就是不保证线程安全;
  • 综合来讲,在须要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操做较多时,更推荐使用 LinkedList。
  • LinkedList 的双向链表也叫双链表,是链表的一种,它的每一个数据结点中都有两个指针,分别指向直接后继和直接前驱。因此,从双向链表中的任意一个结点开始,均可以很方便地访问它的前驱结点和后继结点。

19. ArrayList 和 Vector 的区别是什么?

Java集合面试题(2021最新版)

20. 插入数据时,ArrayList、LinkedList、Vector谁速度较快?阐述 ArrayList、Vector、LinkedList 的存储性能和特性?

  • ArrayList和Vector 底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便增长和插入元素,它们都容许直接按序号索引元素,可是插入元素要涉及数组元素移动等内存操做,因此索引数据快而插入数据慢。
  • Vector 中的方法因为加了 synchronized 修饰,所以 Vector 是线程安全容器,但性能上较ArrayList差
  • LinkedList 使用双向链表实现存储,按序号索引数据须要进行前向或后向遍历,但插入数据时只须要记录当前项的先后项便可,因此 LinkedList 插入速度较快

21. 多线程场景下如何使用 ArrayList?

  • ArrayList 不是线程安全的,若是遇到多线程场景,能够经过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用。例如像下面这样:
List<String> synchronizedList = Collections.synchronizedList(list);
synchronizedList.add("aaa");
synchronizedList.add("bbb");
for (int i = 0; i < synchronizedList.size(); i++) {
System.out.println(synchronizedList.get(i));
}

22. 为何 ArrayList 的 elementData 加上 transient 修饰?

  • ArrayList 中的数组定义以下:

private transient Object[] elementData; 数组

  • 再看一下 ArrayList 的定义:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  • 能够看到 ArrayList 实现了 Serializable 接口,这意味着 ArrayList 支持序列化。transient 的做用是说不但愿 elementData 数组被序列化,重写了 writeObject 实现:
private void writeObject(java.io.ObjectOutputStream s) throws
java.io.IOException{
    *// Write out element count, and any hidden stuff*
    int expectedModCount = modCount;
    s.defaultWriteObject();
    *// Write out array length*
    s.writeint(elementData.length);
    *// Write out all elements in the proper order.*
    for (int i=0; i<size; i++)
    s.writeObject(elementData[i]);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
  • 每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,而后遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减少了序列化以后的文件大小。

23. List 和 Set 的区别

Set接口

24. 说一下 HashSet 的实现原理?

  • HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为present,所以 HashSet 的实现比较简单,相关 HashSet 的操做,基本上都是直接调用底层HashMap 的相关方法来完成,HashSet 不容许重复的值。

25. HashSet如何检查重复?HashSet是如何保证数据不可重复的?

  • 向HashSet 中add ()元素时,判断元素是否存在的依据,不只要比较hash值,同时还要结合equles 方法比较。
  • HashSet 中的add ()方法会使用HashMap 的put()方法。
  • HashMap 的 key 是惟一的,由源码能够看出 HashSet 添加进去的值就是做为HashMap 的key,而且在HashMap中若是K/V相同时,会用新的V覆盖掉旧的V,而后返回旧的V。因此不会重复(HashMap 比较key是否相等是先比较hashcode 再比较equals )。
  • 如下是HashSet 部分源码:
private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;
public HashSet() {
    map = new HashMap<>();
}
public Boolean add(E e) {
    // 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
    return map.put(e, PRESENT)==null;
}

Java集合面试题(2021最新版)

26. HashSet与HashMap的区别

Java集合面试题(2021最新版)

3、Map接口

27. 什么是Hash算法

  • 哈希算法是指把任意长度的二进制映射为固定长度的较小的二进制值,这个较小的二进制值叫作哈希值。

28. 什么是链表

Java集合面试题(2021最新版)

Java集合面试题(2021最新版)

29. 说一下HashMap的实现原理?

Java集合面试题(2021最新版)

30. HashMap在JDK1.7和JDK1.8中有哪些不一样?HashMap的底层实现

Java集合面试题(2021最新版)

Java集合面试题(2021最新版)

Java集合面试题(2021最新版)

Java集合面试题(2021最新版)

31. 什么是红黑树

说道红黑树先讲什么是二叉树 缓存

  • 二叉树简单来讲就是 每个节上能够关联俩个子节点

Java集合面试题(2021最新版)

红黑树

  • 红黑树是一种特殊的二叉查找树。红黑树的每一个结点上都有存储位表示结点的颜色,能够是红 (Red)或黑(Black)。
  • 红黑树的每一个结点是黑色或者红色。当是无论怎么样他的根结点是黑色。每一个叶子结点(叶子结点表明终结、结尾的节点)也是黑色 注意:这里叶子结点,是指为空(NIL或NULL)的叶子结点!。
  • 若是一个结点是红色的,则它的子结点必须是黑色的。
  • 每一个结点到叶子结点NIL所通过的黑色结点的个数同样的。确保没有一条路径会比其余路径长出俩倍,因此红黑树是相对接近平衡的二叉树的!
  • 红黑树的基本操做是添加、删除。在对红黑树进行添加或删除以后,都会用到旋转方法。为何呢?道理很简单,添加或删除红黑树中的结点以后,红黑树的结构就发生了变化,可能不知足上面三条性质,也就再也不是一颗红黑树了,而是一颗普通的树。而经过旋转和变色,可使这颗树从新成为红黑树。简单点说,旋转和变色的目的是让树保持红黑树的特性。

32. HashMap的put方法的具体流程?

Java集合面试题(2021最新版)

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//实现Map.put和相关方法
final V putVal(int hash, K key, V value, Boolean onlyIfAbsent,
Boolean evict) {
    Node<K,V>[] tab;
    Node<K,V> p;
    int n, i;
    // 步骤①:tab为空则建立
    // table未初始化或者长度为0,进行扩容
    if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;
    // 步骤②:计算index,并对null作处理
    // (n - 1) & hash 肯定元素存放在哪一个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数
    组中)
    if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);
    // 桶中已经存在元素 else {
        Node<K,V> e;
        K k;
        // 步骤③:节点key存在,直接覆盖value
        // 比较桶中第一个元素(数组中的结点)的hash值相等,key相等
        if (p.hash == hash &&
        ((k = p.key) == key || (key != null && key.equals(k))))
        // 将第一个元素赋值给e,用e来记录
        e = p;
        // 步骤④:判断该链为红黑树
        // hash值不相等,即key不相等;为红黑树结点
        // 若是当前元素类型为TreeNode,表示为红黑树,putTreeVal返回待存放的node, e可能为null else if (p instanceof TreeNode)
        // 放入树中
        e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        // 步骤⑤:该链为链表
        // 为链表结点 else {
            // 在链表最末插入结点
            for (int binCount = 0; ; ++binCount) {
                // 到达链表的尾部
                //判断该链表尾部指针是否是空的
                if ((e = p.next) == null) {
                    // 在尾部插入新结点
                    p.next = newNode(hash, key, value, null);
                    //判断链表的长度是否达到转化红黑树的临界值,临界值为8
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                    //链表结构转树形结构
                    treeifyBin(tab, hash);
                    // 跳出循环
                    break;
                }
                // 判断链表中结点的key值与插入的元素的key值是否相等
                if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                // 相等,跳出循环
                break;
                // 用于遍历桶中的链表,与前面的e = p.next组合,能够遍历链表
                p = e;
            }
        }
        //判断当前的key已经存在的状况下,再来一个相同的hash值、key值时,返回新来的value这个
        值
        if (e != null) {
            // 记录e的value
            V oldValue = e.value;
            // onlyIfAbsent为false或者旧值为null
            if (!onlyIfAbsent || oldValue == null)
            //用新值替换旧值
            e.value = value;
            // 访问后回调
            afterNodeAccess(e);
            // 返回旧值
            return oldValue;
        }
    }
    // 结构性修改
    ++modCount;
    // 步骤⑥:超过最大容量就扩容
    // 实际大小大于阈值则扩容
    if (++size > threshold)
    resize();
    // 插入后回调
    afterNodeInsertion(evict);
    return null;
}
  1. 判断键值对数组tablei是否为空或为null,不然执行resize()进行扩容;
  2. 根据键值key计算hash值获得插入的数组索引i,若是tablei==null,直接新建节点添加,转向⑥,若是tablei不为空,转向③;
  3. 判断tablei的首个元素是否和key同样,若是相同直接覆盖value,不然转向④,这里的相同指的是hashCode以及equals;
  4. 判断tablei 是否为treeNode,即tablei 是不是红黑树,若是是红黑树,则直接在树中插入键值对,不然转向5;
  5. 遍历tablei,判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操做,不然进行链表的插入操做;遍历过程当中若发现key已经存在直接覆盖value便可;
  6. 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,若是超过,进行扩容。

33. HashMap的扩容操做是怎么实现的?

Java集合面试题(2021最新版)

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    //oldTab指向hash桶数组
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        //若是oldCap不为空的话,就是hash桶数组不为空
        if (oldCap >= MAXIMUM_CAPACITY) {
            //若是大于最大容量了,就赋值为整数最大的阀值
            threshold = Integer.MAX_VALUE;
            return oldTab;
            //返回
        }
        //若是当前hash桶数组的长度在扩容后仍然小于最大容量 而且oldCap大于默认值16 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
        oldCap >= DEFAULT_INITIAL_CAPACITY)
        newThr = oldThr << 1;
        // double threshold 双倍扩容阀值threshold
    }
    // 旧的容量为0,但threshold大于零,表明有参构造有cap传入,threshold已经被初始化
    成最小2的n次幂
    // 直接将该值赋给新的容量 else if (oldThr > 0) // initial capacity was placed in threshold
    newCap = oldThr;
    // 无参构造建立的map,给出默认容量和threshold 16, 16*0.75 else {
        // zero initial threshold signifies using
        defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 新的threshold = 新的cap * 0.75
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft <
        (float)MAXIMUM_CAPACITY ?
        (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    // 计算出新的数组长度后赋给当前成员变量table
    @SuppressWarnings({
        "rawtypes","unchecked"
    }
    )
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    //新建hash桶数组
    table = newTab;
    //将新数组的值复制给旧的hash桶数组
    // 若是原先的数组没有初始化,那么resize的初始化工做到此结束,不然进入扩容元素重排逻辑,使
    其均匀的分散
    if (oldTab != null) {
        // 遍历新数组的全部桶下标
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                // 旧数组的桶下标赋给临时变量e,而且解除旧数组中的引用,不然就数组无
                法被GC回收
                oldTab[j] = null;
                // 若是e.next==null,表明桶中就一个元素,不存在链表或者红黑树
                if (e.next == null)
                // 用一样的hash映射算法把该元素加入新的数组
                newTab[e.hash & (newCap - 1)] = e;
                // 若是e是TreeNode而且e.next!=null,那么处理树中元素的重排 else if (e instanceof TreeNode)
                ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                // e是链表的头而且e.next!=null,那么处理链表中元素重排 else {
                    // preserve order
                    // loHead,loTail 表明扩容后不用变换下标,见注1
                    Node<K,V> loHead = null, loTail = null;
                    // hiHead,hiTail 表明扩容后变换下标,见注1
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    // 遍历链表
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                            // 初始化head指向链表当前元素e,e不必定是链表的
                            第一个元素,初始化后loHead
                            // 表明下标保持不变的链表的头元素
                            loHead = e; else
                            // loTail.next指向当前e
                            loTail.next = e;
                            // loTail指向当前的元素e
                            // 初始化后,loTail和loHead指向相同的内存,因此当
                            loTail.next指向下一个元素时,
                            // 底层数组中的元素的next引用也相应发生变化,形成lowHead.next.next.....
                            // 跟随loTail同步,使得lowHead能够连接到全部属于该链
                            表的元素。
                            loTail = e;
                        } else {
                            if (hiTail == null)
                            // 初始化head指向链表当前元素e, 初始化后hiHead
                            表明下标更改的链表头元素
                            hiHead = e; else
                            hiTail.next = e;
                            hiTail = e;
                        }
                    }
                    while ((e = next) != null);
                    // 遍历结束, 将tail指向null,并把链表头放入新数组的相应下标,
                    造成新的映射。
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

34. HashMap是怎么解决哈希冲突的?

  • 答:在解决这个问题以前,咱们首先须要知道什么是哈希冲突,而在了解哈希冲突以前咱们还要知道什么是哈希才行;

Java集合面试题(2021最新版)

Java集合面试题(2021最新版)

Java集合面试题(2021最新版)

35. 可否使用任何类做为 Map 的 key?

可使用任何类做为 Map 的 key,然而在使用以前,须要考虑如下几点:

  • 若是类重写了 equals() 方法,也应该重写 hashCode() 方法。
  • 类的全部实例须要遵循与 equals() 和 hashCode() 相关的规则。
  • 若是一个类没有使用 equals(),不该该在 hashCode() 中使用它。
  • 用户自定义 Key 类最佳实践是使之为不可变的,这样 hashCode() 值能够被缓存起来,拥有更好的性能。不可变的类也能够确保 hashCode() 和 equals() 在将来不会改变,这样就会解决与可变相关的问题了。

36. 为何HashMap中String、Integer这样的包装类适合做为K?

Java集合面试题(2021最新版)

37. 若是使用Object做为HashMap的Key,应该怎么办呢?

Java集合面试题(2021最新版)

38. HashMap为何不直接使用hashCode()处理后的哈希值直接做为table的下标?

Java集合面试题(2021最新版)

39. HashMap 的长度为何是2的幂次方

Java集合面试题(2021最新版)

40. HashMap 与 HashTable 有什么区别?

  1. 线程安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都通过 synchronized 修饰。(若是你要保证线程安全的话就使用 ConcurrentHashMap );
  2. 效率: 由于线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;(若是你要保证线程安全的话就使用 ConcurrentHashMap );
  3. 对Null key 和Null value的支持: HashMap 中,null 能够做为键,这样的键只有一个,能够有一个或多个键所对应的值为 null。可是在 HashTable 中 put 进的键值只要有一个 null,直接抛NullPointerException。
  4. 初始容量大小和每次扩充容量大小的不一样
  5. 建立时若是不指定容量初始值,Hashtable 默认的初始大小为11,以后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。以后每次扩充,容量变为原来的2倍。
  6. 建立时若是给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。也就是说 HashMap 老是使用2的幂做为哈希表的大小,后面会介绍到为何是2的幂次方。
  7. 底层数据结构: JDK1.8 之后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减小搜索时间。Hashtable 没有这样的机制。
  8. 推荐使用:在 Hashtable 的类注释能够看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,若是须要多线程使用则用 ConcurrentHashMap 替代。

41. 什么是TreeMap 简介

  • TreeMap 是一个有序的key-value集合,它是经过红黑树实现的。
  • TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的天然顺序进行排序,或者根据建立映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
  • TreeMap是线程非同步的。

42. 如何决定使用 HashMap 仍是 TreeMap?

  • 对于在Map中插入、删除和定位元素这类操做,HashMap是最好的选择。然而,假如你须要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。

43. HashMap 和 ConcurrentHashMap 的区别

  1. ConcurrentHashMap对整个桶数组进行了分割分段(Segment),而后在每个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8以后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)
  2. HashMap的键值对容许有null,可是ConCurrentHashMap都不容许。

44. ConcurrentHashMap 和 Hashtable 的区别?

Java集合面试题(2021最新版)

Java集合面试题(2021最新版)

Java集合面试题(2021最新版)

三、JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 Node: 链表节点):

Java集合面试题(2021最新版)

45. ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?

Java集合面试题(2021最新版)

Java集合面试题(2021最新版)

Java集合面试题(2021最新版)

  • 若是相应位置的Node不为空,且当前该节点不处于移动状态,则对该节点加synchronized锁,若是该节点的hash不小于0,则遍历链表更新节点或插入新节点;
if (fh >= 0) {
    binCount = 1;
    for (Node<K,V> e = f;; ++binCount) {
        K ek;
        if (e.hash == hash &&
        ((ek = e.key) == key ||
        (ek != null && key.equals(ek)))) {
            oldVal = e.val;
            if (!onlyIfAbsent)
            e.val = value;
            break;
        }
        Node<K,V> pred = e;
        if ((e = e.next) == null) {
            pred.next = new Node<K,V>(hash, key, value, null);
            break;
        }
    }
}
  1. 若是该节点是TreeBin类型的节点,说明是红黑树结构,则经过putTreeVal方法往红黑树中插入节点;若是binCount不为0,说明put操做对数据产生了影响,若是当前链表的个数达到8个,则经过treeifyBin方法转化为红黑树,若是oldVal不为空,说明是一次更新操做,没有对元素个数产生影响,则直接返回旧值;
  2. 若是插入的是一个新节点,则执行addCount()方法尝试更新元素个数baseCount;

4、辅助工具类

46. Array 和 ArrayList 有何区别?

  • Array 能够存储基本数据类型和对象,ArrayList 只能存储对象。
  • Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
  • Array 内置方法没有 ArrayList 多,好比 addAll、removeAll、iteration 等方法只有 ArrayList有。

47. 如何实现 Array 和 List 之间的转换?

  • Array 转 List: Arrays. asList(array) ;
  • List 转 Array:List 的 toArray() 方法。

48. comparable 和 comparator的区别?

  • comparable接口其实是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序
  • comparator接口其实是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用来排序
  • 通常咱们须要对一个集合使用自定义排序时,咱们就要重写compareTo方法或compare方法,当咱们须要对某一个集合实现两种排序方式,好比一个song对象中的歌名和歌手名分别采用一种排序方法的话,咱们能够重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种表明咱们只能使用两个参数版的Collections.sort().

49. Collection 和 Collections 有什么区别?

  • java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操做的通用接口方法。Collection接口在Java 类库中有不少具体的实现。Collection接口的意义是为各类具体的集合提供了最大化的统一操做方式,其直接继承接口有List与Set。
  • Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各类操做。

50. TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素?

Java集合面试题(2021最新版)

51. Collection 和 Collections 有什么区别?

  • java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操做的通用接口方法。Collection接口在Java 类库中有不少具体的实现。Collection接口的意义是为各类具体的集合提供了最大化的统一操做方式,其直接继承接口有List与Set。
  • Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各类操做。