集合面试题

1.数组

  • 数组:连续内存空间存储的相同数据类型的线性数据结构
  • 寻址公式:baseAddress + i*dataTypeSize
  • 查找时间复杂度:
    • 随机查找O(1)
    • 查找未知下标O(n)
    • 查找未知下标但排序O(logn)
  • 插入和删除时间复杂度
    • O(n)

2.ArrayList

  • 动态数组实现
  • 初始为0,第一次添加的时候初始化为10
  • 扩容为1.5倍,每次拷贝
  • 添加数据逻辑:
    • 确保数组已使用的size+1之后足够存下下一个数据
    • 计算数组的容量,如果当前的数组已使用长度+1后大于(等与不会扩容)当前的数组长度,扩容1.5倍
    • 确保有地方存数据时,添加到size
    • 返回true

3.如何实现数组和List的转换

  • 数组转List:使用JDK中的Arrays工具类的asList方法
    • 数组转List后,修改数组,新的List会受影响
    • 因为:底层是使用内部类ArrayList来构造的集合,是对我们传入的集合进行包装,最终指向的都是同一个内存地址
  • List转数组:使用List的toArray方法,无参toArray方法返回Object数组,传入初始化长度的数组对象,返回该对象数组
    • List转数组后,修改List,新的数组不会受影响
    • 因为:底层是进行了数组的拷贝,跟原来的元素就没啥关系了

4.链表

链表方向时间复杂度
单向链表单向头O(1),其他O(n)
双向链表双向头尾O(1),其它O(n),给定节点O(1)

5.ArrayList和LinkedList的区别是什么

  • 同:都线程不安全
  • 异:底层数据结构,时间复杂度,内存空间占用
    • ArrayList:底层数组
    • LinkedList:底层双向链表

6.红黑树

  • 红黑数:自平衡的二叉搜索树(BST),也叫平衡二叉B树
  • 红黑规则:
    • 1.节点要么红,要么黑
    • 2.根是黑色
    • 3.叶子节点都是黑色的空节点
    • 4.红黑树中的红色节点的子节点都是黑色
    • 5.从任意一节点到叶子节点的路径都包含数目相同的黑色节点
  • 时间复杂度:
    • 查找:O(logn)
    • 添加:整体O(logn),因为得先查找O(logn)再添加后旋转O(1)
    • 删除:整体O(logn),因为得先查找O(logn)再删除后旋转O(1)

7.散列表

  • 散列表:哈希 (hash) 表,根据键 (key) 直接访问内存存储位置值 (value) 的数据结构,是根据数组演化而来的,利用了数组支持按照下标随机访问数据的特性
  • 散列函数:讲键 (key) 映射为数组下标的函数
  • 哈希碰撞:散列冲突,多个key映射到同一个下标的位置
  • 解决冲突的办法:
    • 拉链法:每个下标的位置都称为桶 (bucker) 或槽 (slot) ,没个桶都会对应一个链表,存放相同散列值相同的元素
      • 时间复杂度:插入O(1),查找删除O(1),O(n)或O(logn)

8.HashMap的实现原理

  • 数据结构:底层使用的是hash表的数据结构,数组链表,1.8以后加入了红黑树
  • 1.当向HashMap中put数据时,利用key的hashCode重新计算hash计算出当前对象的元素在数组中的下标
  • 2.存储时,如果出现hash值相同的key,两种情况:
    • a.如果key相同,则覆盖原始值
    • b.如果key不同(出现冲突),则将key-value放入链表或红黑树中
  • 3.获取时,直接找到hash对应的下标,再进一步判断key是否相同,从而找到对应值

9.HashMap的jdk1.7和1.8有什么区别

  • jdk1.7及之前是拉链法,采用头插法加入数据
  • jdk1.8及之后加入了红黑树,采用尾插法加入数据
    • 当链表的长度大于等于8,且数组的长度大于64时转化为红黑树
    • 扩容时,红黑树拆分成的树结点数小于等于临界值6个,则退化为链表

10.HashMap的put方法的具体流程

HashMap的put方法的具体流程

  • 1.判断键值对数组table是否为null,否则执行resize()进行扩容(初始化)
  • 2.根据键值key计算hash值得到数组索引
  • 3.判断table[i]==null,条件成立,直接新建节点添加
  • 4.如果table[i]==null,条件不成立
    • 4.1判断table[i]的首个元素是否和key一样,如果相同则直接覆盖value
    • 4.2不相同,再判断table[i]是否是红黑树,如果是红黑树,则直接在数中加入键值对
    • 4.3若不是树,再遍历table[i],判断链表长度是否大于8且数组的长度大于64,不满足条件在链表的尾部插入数据,满足条件则将链表转化为红黑树,在红黑树中插入操作,遍历过程中若是发现key则直接覆盖value
  • 5.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold(数组长度*0.75),如果超过,进行扩容

11.HashMap的扩容

HashMap扩容- 在添加元素或初始化的时候都需要调用resize方法进行扩容,第一次添加数据初始化数组长度是16,以后每次都是达到了扩容阈值(数组长度*0.75)

  • 每次扩容的时候,都是扩容之前的2倍
  • 扩容之后,会创建新的数组,将老数组中的数据挪动到新的数组中
    • 没有hash冲突的节点,则使用e.hash & (newCap - 1)计算新数组的索引位置
    • 如果是红黑树,走红黑树的添加
    • 如果是链表,则需要遍历链表,可能需要拆分链表,判断(e.hash & odlCap)是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组的大小的这个位置

12.HashMap的寻址算法

  • 扰动算法:计算hashCode值后,再调用方法进行二次hash,hashCode值右移16位再异或运算,使hash值更加均匀,减少hash冲突
  • hash & (n-1):得到的数组中的索引,代替取模,性能更好,数组长度必须是2的n次幂

13.为什么HashMap的数组长度一定是2的次幂

  • 计算索引时效率更高:如果是2的n次幂可以使用位于运算代替取模
  • 扩容时重新计算索引效率更高:hash & oldCap == 0元素留在原来的位置,否则新位置 = 旧位置 + oldCap

12.HashMap在jdk1.7情况下的多线程死循环问题

  • jdk1.7使用的数据结构是:数组+链表,扩容是头插法,在数据迁移过程中,没有新对象产生,只是改变了对象的引用
  • 问题的发生:
    • 1.有线程1和线程2准备扩容拷贝链表
    • 2.线程1的e1和next1和线程2的e2和next2分别指向同一个位置
      • e是指将要迁移的节点
      • next是指下一个要迁移的节点
    • 3.线程1先开始,线程2先睡
    • 4.线程1将原来的链表运用头插法拷贝到了新的位置
    • 5.然后线程2醒来继续操作,原先线程2指向的e2和next2,由于线程1的操作,已经逆序,此时next2只指向的下一个节点是e2
    • 6.线程2继续执行头插法进行拷贝,会导致e2指向next2,next2指向e2的请况,然后导致死循环
  • 解决方案:jdk8扩容使用尾插法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暖阳爱学计算机

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值