2021春招必备Java面试题大全(五)Java-集合部分 持续更新

  1. 什么是集合
  • 集合框架:用于存储数据的容器。
  • 集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。 任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。
  • 接口:表示集合的抽象数据类型。接口允许我们操作集合时不必关注具体实现,从而达到“多态”。在面 向对象编程语言中,接口通常用来形成规范。
  • 实现:集合接口的具体实现,是重用性很高的数据结构。
  • 算法:在一个实现了某个集合框架中的接口的对象身上完成某种有用的计算的方法,例如查找、排序 等。这些算法通常是多态的,因为相同的方法可以在同一个接口被多个类实现时有不同的表现。事实 上,算法是可复用的函数。
  1. 集合和数组的区别
  • 数组是固定长度的;集合可变长度的。
  • 数组可以存储基本数据类型,也可以存储引用数据类型;
  • 集合只能存储引用数据类型。
  • 数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。
  1. 常用的集合类有哪些?

    Map接口和Collection接口是所有集合框架的父接口:

  • Collection接口的子接口包括:Set接口和List接口和Queue借口
  • Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及
    Properties等
  • Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
  • List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
  1. **List,Set,Map三者的区别?List、Set、Map 是否继承自 Collection 接 口?List、Map、Set 三个接口存取元素时,各有什么特点?
    **
    2021春招必备Java面试题大全(五)Java-集合部分 持续更新_第1张图片

2021春招必备Java面试题大全(五)Java-集合部分 持续更新_第2张图片

  • java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、List、Queue三种子接口。 我们比较常用的是Set、List,Map接口不是collection的子接口。
  • Collection集合主要有List和Set两大接口
    List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个 null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。 Set:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null 元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。
  • Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。 Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
  1. 集合框架底层数据结构
  • Collection

  • List
    – ArrayLIst: Object数组
    – LinkedList: 双向环形链表
    – vector: Object数组

  • Set
    – HashSet是基于HashMap实现的 在构造器中就会new一个HashMap, HashMap的Key就是HashSet列。而Value就是一个默认值
    –LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
    – TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)

  • Map
    –HashMap JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为 了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当 链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
    –LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构 即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链 表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺 序相关逻辑。
    –HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存 在的
    – TreeMap: 红黑树(自平衡的排序二叉树)

  1. 说一下 ArrayList 的优缺点
  • ArrayList的优点如下:
  • ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查 找的时候非常快。
  • ArrayList 在顺序添加一个元素的时候非常方便。
  • ArrayList 的缺点如下: 删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。
  • 插入元素的时候,也需要做一次元素复制操作,缺点同上。 ArrayList 比较适合顺序添加、随机访问的场景。
  1. 说一下 HashSet 的实现原理?

HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为 PRESENT,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。

  1. 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 )

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;
}
  • hashCode()与equals()的相关规定:
    – 如果两个对象相等,则hashcode一定也是相同的
    – 两个对象相等,对两个equals方法返回true
    – 两个对象有相同的hashcode值,它们也不一定是相等的
    – 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
    – hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个
    对象无论如何都不会相等(即使这两个对象指向相同的数据)。
  • ==与equals的区别
    –==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存 空间的值是不是相同
    – ==是指对内存地址进行比较 equals()是对字符串的内容进行比较
    – ==指引用是否相同 equals()指的 是值是否相同
  1. BlockingQueue是什么?

Java.util.concurrent.BlockingQueue是一个队列,在进行检索或移除一个元素的时候,它会等待队列变 为非空;当在添加一个元素时,它会等待队列中的可用空间。BlockingQueue接口是Java集合框架的一 部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的 对象,因为它都在BlockingQueue的实现类中被处理了。Java提供了集中BlockingQueue的实现,比如 ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。

  1. Queue 中 poll()和 remove()有什么区别?
  • 相同点:都是返回第一个元素,并在队列中删除返回的对象。
  • 不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异 常。
  1. 说一下 HashMap 的实现原理?

HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作, 并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

HashMap 基于 Hash 算法实现的

  • 当我们往Hashmap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
  • 存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果 key不同(出现冲突),则将当前的key-value放入链表中
  • 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
  • 理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。

需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)

  1. HashMap在JDK1.7和JDK1.8中有哪些不同?HashMap的底层实现

在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除 困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做拉链法的方式可以解决哈希冲突。

  • JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
  • JDK1.8之后相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将 链表转化为红黑树,以减少搜索时间。

JDK1.8主要解决或优化了一下问题:

  • resize 扩容优化
  • 引入了红黑树,目的是避免单条链表过长而影响查询效率,红黑树算法请参考
  • 解决了多线程死循环问题,但仍是非线程安全的,多线程时可能会造成数据丢失问题。

2021春招必备Java面试题大全(五)Java-集合部分 持续更新_第3张图片

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

当我们put的时候,首先计算 key 的 hash 值,这里调用了 hash 方法, hash 方法实际是让 key.hashCode() 与 key.hashCode()>>>16 进行异或操作,高16bit补0,一个数和0异或不变,所以
hash 函数大概的作用就是:高16bit不变,低16bit和高16bit做了一个异或,目的是减少碰撞。按照函 数注释,因为bucket数组大小是2的幂,计算下标 index = (table.length - 1) & hash ,如果不做 hash 处理,相当于散列生效的只有几个低 bit 位,为了减少散列的碰撞,设计者综合考虑了速度、作 用、质量之后,使用高16bit和低16bit异或来简单处理减少碰撞,而且JDK8中用了复杂度 O(logn)的 树结构来提升碰撞下的性能。

  • put方法的原理
    1 判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
    2根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向6,如果table[i]不为空,转向3;
    3.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向4,这里的相同指的是hashCode以及equals;
    4.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向5;
    5.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操
    作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
    6.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
  1. HashMap的扩容操作是怎么实现的?
    1.在jdk1.8中,resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容;
    2.每次扩展的时候,都是扩展2倍;
    3.扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。

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

什么是哈希冲突? 当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)。

在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除 困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各 自的优势,使用一种叫做链地址法的方式可以解决哈希冲突:

HashMap的hash方法

static final int hash(Object key) {
     
    int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
// 与自己右移16位 进行异或运算(高低位异或)
}

这比在JDK 1.7中,更为简洁,相比在1.7中的4次位运算,5次异或运算(9次扰动),在1.8中,只进行 了1次位运算和1次异或运算(2次扰动);

  • 简单总结一下HashMap是使用了哪些方法来有效解决哈希冲突的
    – 使用链地址法(使用散列表)来链接拥有相同hash值的数据;
    – 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;
    – 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;
  1. 能否使用任何类作为 Map 的 key?

可以使用任何类作为 Map 的 key,然而在使用之前,需要考虑以下几点:

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

你可能感兴趣的:(jvm,java面试,2021春招,java,集合,底层应用开发)