Java面试:集合篇

Java常见的集合类

Java提供常见的集合有那些?

单列集合:Collection,有两个接口分别为List和Set

  • List的实现类有:ArrayList、LinkList
  • set的实现类有:HashSet、TreeSet

双列集合:Map。其实现类有

  • 线程不安全的:HashMap、TreeMap
  • 线程安全的:ConcurrentHashMap

List

ArrayList底层是如何实现的?

ArrayList底层是通过动态数组来实现的。

  • 在添加元素时首先确保当前数组已使用长度(size)+1能存储下一个元素,如果+1后大于数组长度通过grow方法进行扩容(扩大1.5倍),否则将新元素添加到size的位置

ArrayList list=new ArrayList(10)中的list扩容几次?

  • 在ArrayList源码当中提供了一个带参构造方法,这个参数就是集合的初始长度,这里就是指定了初始长度为10,没有发生扩容

数组和List之间的转换?

数组转List:

  • 可以使用Arrays中的asList方法进行转换
  • 这种方法得到的List是一个固定长度的List,和数组共享相同的内存地址,因此修改原数组内容List的内容也会发生改变

List转数组:

  • 可以使用List的toArray方法
  • 底层是进行内容拷贝,得到一个新的数组对象。对该数组修改内容不会影响List

ArrayList和LinkedList的区别是什么?

  • 底层数据结构:ArrayList是基于动态数组实现的;LinkList是基于双向链表实现的
  • 随机访问:ArrayList支持通过索引快速随机访问,而LinkList不支持
  • 插入和删除操作:如果给定一个节点进行插入删除时,ArrayList需要移动该节点的后续节点,但是LinkList只需要修改指针即可。但是若不给定节点,它们俩插入删除效率都为o(n)
  • 内存占有:LinkedList 是双向链表需要存储数据和两个指针,而ArrayList只需要存储数据即可

ArrayList 和 LinkedList 不是线程安全的,在项目中是如何解决这个的线程安全问题的?

  • 使用集合时,优先在方法内使用,定义局部变量,这样就不会有线程安全问题了
  • 如果非要在成员变量中使用,可以通过collection的synchronizedList方法将ArrayList转换为线程安全的,将LinkList转换为ConcurrentLinkedqueue来使用

HashMap

说下HashMap的实现原理?

  • 底层是通过hash表结构。即数组+(链表|红黑树)。数组每个元素称为桶,每个桶对应一个链表或者红黑树
  • 在添加元素时,通过hash函数(hashcode)计算key的hash码进而来确定其在元素的下标。若该下标上存在元素且key相同就进行替换,不同则将健值存入该桶的链表或红黑树中
  • 负载因子表示hash表填充程度的参数,默认为0.75,。若hash表填充超过了元素这个比例则会发生扩容(扩两倍)

HashMap的jdk1.7和jdk1.8有什么区别?

  • JDK1.8之前采用的拉链法,数组+链表
  • JDK1.8之后采用数组+链表+红黑树,链表长度大于8且数组长度大于64则会从链表 转化为红黑树

你能说下HashMap的put方法的具体流程吗?

  • 先判断hash表是否为空,如果为空通过resize进行扩容(初始化)
  • 通过hash函数计算键的数组位置(称为桶),通过桶为空,直接将元素放入桶中
  • 如果桶已经存在节点,则对链表或红黑树进行遍历
  • 如果遍历节点的键与要插入的键相同(equals判断),直接进行覆盖
  • 如果不同且桶中为红黑树,走红黑树添加逻辑即可。
  • 若桶中为链表,在链表尾部插入元素,若插入元素后链表长度是否大于8且桶的数量大于64将链表转换为红黑树
  • 插入成功后若存有元素的桶的数量大于桶数量乘以负载因子,则对hashmap进行扩容

你多次介绍了hsahmap的扩容,能讲一讲HashMap的扩 容机制吗?

  • 初始时通过resize方法对散列表进行初始化,其初始化长度为16,后续当达到扩容阈值时对散列表进行扩容
  • 扩容时,首先会创建一个数容量为之前数组两倍的数组。然后将原数组的元素迁移到新数组当中
  • 对于没有冲突的桶,直接使用e.hash&(newCap-1)计算其在新数组中位置
  • 如果是红黑树,遍历红黑树,走红黑树的添加
  • 如果是链表,遍历链表,判断e.hash&oldCap是否为0。如果为0则在新数组中的位置与原数组相同。若为1在新数组的位置为原数组位置+旧数组长度

你说的通过hash计算后找到数组的下标,是如何找到的 呢,你了解hashMap的寻址算法吗?

通过哈希码来计算键值对应的存储位置(桶的索引)具体步骤如下

  • 实现HashMap会调用键的hashCode方法来获取其哈希码
  • 然后将右移16位得到的哈希码与原哈希码按位进行异或运算得到最终的hash码
  • 将最总的hash码与数组长度取模得到其桶的值(数组下标)。(当然为了提高找数组下标的效率,可以通过e.hash&(n-1))

为何HashMap的数组长度一定是2的次幂?

  • 计算索引时效率更高。可以使用位与运算代替取模
  • 扩容时计算索引效率更高

我看你对hashmap了解的挺深入的,你知道hashmap在1.7情况下的多线程死循环问题吗?

  • jdk1.7采用的数组+链表,当数组在扩容时,因为链表是头插法,在进行数据迁移时,有可能会导致死循环。
  • 例子:假如有俩线程,线程1正在读取hashmap,准备扩容时线程2介入。线程而也读取hashmap并直接扩容。由于采用头插法对链表数据进行迁移,索引迁移完成后原顺序AB会变为BA。这时线程1再进行操作时,就会形成BAB的死循环
  • jdk1.8中采用尾插法对链表数据进行迁移,使得新数组中链表元素顺序与扩容前一致

hashmap是线程安全的吗?

  • 不是线程安全的
  • 可以采用ConcurrentHashMap进行使用,它是一个线程安全的 HashMap

HashSet与HashMap的区别?

  • hashset内部是通过hashmap来实现的,hashset中的元素被存储为hashmap的键,其值被默认为一个常量object。其存储的为独一无二的元素
  • hashmap存储的是键值对,每个键对应一个值,通过键来找到其=值

HashTable与HashMap的区别?

  • 线程安全性:HashTable加了synchronized锁是线程安全的,而HashMap不是
  • 扩容方式:HashTable扩容是当前容量+1。而HashMap是当前容量翻倍
  • 空值:HashTable存储数据时不能为null,而hashmap可以
  • 数据结构:HashTable是数组+链表,而hashmap1.8之后是数组+链表+红黑树

Java面试的最后一章。后续会更新微服务、设计模式、场景题的一些总结,敬请期待!

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值