Java集合框架面试题

java 中有的容器有哪些


1-Collection 2-Map

collection 和 collections 有什么区别

  • collection 是 java 集合的接口,其实现类有 List 和 Set
  • collections 是一个工具类,里面包含了许多有关集合静态的方法,可以直接调用。

List、Set、Map 的区别

  • List 有序,元素可以重复
  • Set 无序,元素不可重复
  • Map 无序,键值对映射存储,key 不能重复,value 可重复

List 下面有哪些子接口,区别是什么?

  1. ArryList:底层是数组,查询快、增删慢,效率高,线程不安全。
  2. LinkedList:底层是链表,查询慢、删除快,效率高,线程不安全。
  3. Vector:底层是数组,查询快、增删慢,效率低,线程安全。(在 ArryList 的每个方法里面都添加了 synchronized)

Set下面的子接口是哪些?其作用是什么?

  1. HashSet:jdk1.7的版本是由数组+链表组成,jdk1.8后是由数组+链表+红黑树组成。HashSet底层是由哈希表组成,其作用就是通过hashcode计算出哈希值进行储存,不能够存储重复的数据,且无索引。
  2. TreeSet:它的底层是TreeMap,也就是红黑树(平衡二叉树),它存储的数据也是不可重复且对储存的数据进行了排序,可以自定义添加比较器进行查询。

ArryList的扩容机制

ArryList本身是一个数组,使用它时它有固定的长度,每当要去插入数据到数组中时,它会检查是否会溢出,如果溢出,那么它就会进行扩容,形成一个长度为原来1.5的新数组,再原数组的数据拷贝过去。

ArryList是怎么序列化的

ArryList序列化可不一样,它用来transient来修饰了数组成员变量elementData,而关键transient关键字的作用就是不让被修饰的成员变量进行序列化。所以ArryList是通过readObjec和writeObject,也就是ObjectOutputStream和ObjectInputStream流来进行自定义序列化和反序列化。

有哪几种实现ArryList线程安全的方法

  • 用vector来代替ArryList。(不推荐,因为vector是一个遗忘类)
  • 用Collections.sysnchronizedList包装list,然后操作包装后的lits。
  • 使用CopyonWriteArryList代替ArryList。
  • 使用程序同步机制来操作控制ArryList的读写。

谈谈你对fail-fast(快速失败)和fail-safe(安全失败)的理解

fail-fast(快速失败)

  • 快速失败就java集合一种错误检测机制。
  • 就是说一个线程A在对集合X进行遍历时,另一个线程B对集合X进行了修改(增加、删除)操作时,会抛出异常
  • 原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个modCount变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
  • 注意:这里异常的抛出条件是检测到modCount != expectedmodCount这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
  • 场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改),比如ArrayList 类。

fail-safe(安全失败)

  • 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
  • 原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
  • 缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
  • 场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改,比如CopyOnWriteArrayList类。

CopyOnWriteArrayList了解多少

CopyOnWriteArrayList就是线程安全版本的ArrayList。

它的名字叫 copyOnwrite——写时复制,已经明示了它的原理。

CopyOnWriteArrayList采用了一种读写分离的并发策略。CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。至于写操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。

HashMap的数据结构

jdk1.7的数据结构是:数组+链表。但是在jdk1.8过后就由数组+链表+红黑树组成了。

数据结构如下:

其中,数桶组是来存储数据元素的,链表是用来解冲突的,红黑树是为了提高查询效率。

注意事项:

  • 数据元素通过映射关系,也就是散列表,映射到桶数组对应索引的位位置。
  • 如果发生冲突,从冲突的位置拉一个链表,插入冲突的元素。
  • 如果链表长度>=8数组大小>=64,链表就转为红黑树。
  • 如果红黑树节点个数<6,转为链表。

你对红黑树了解多少?为什么不用二叉树/平衡树呢?

红黑树本质上是一种二叉查找树,为了保持平衡,它又在二叉查找树的基础上增加了一些规则:

  • 每个节点要么是红色,要么是黑色;
  • 根节点永远是黑色的;
  • 所有的叶子节点都是是黑色的(注意这里说叶子节点其实是图中的NULL节点);
  • 每个红色节点的两个子节点一定都是黑色;
  • 从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点;

之所以不用二叉树:

红黑树是一种平衡的二叉树,插入、删除、查找的最坏时间复杂度都为O(logn),避免了二叉树最坏情况下的O(n)时间复杂度。

红黑树怎么保持平衡的知道吗?

红黑树有两种方式保持平衡:旋转和染色。
旋转:旋转分为两种,左旋和右旋。
染色:变换红黑的颜色。

为什么HashMap的容量是2的倍数呢?

  • 第一个原因是为了方便哈希取余:
    将元素放在table数组上面,是用hash值%数组大小定位位置,而HashMap是用hash值&(数组大小-1),却能和前面达到一样的效果,这就得益于HashMap的大小是2的倍数,2的倍数意味着该数的二进制位只有一位为1,而该数-1就可以得到二进制位上1变成0,后面的0变成1,再通过&运算,就可以得到和%一样的效果,并且位运算比%的效率高得多

    HashMap的容量是2的n次幂时,(n-1)的2进制也就是1111111***111这样形式的,这样与添加元素的hash值进行位运算时,能够充分的散列,使得添加的元素均匀分布在HashMap的每个位置上,减少hash碰撞。
  • 第二个方面是在扩容时,利用扩容后的大小也是2的倍数,将已经产生hash碰撞的元素完美的转移到新的table中去,HashMap中的元素在超过负载因子*HashMap大小时就会产生扩容。

解决哈希冲突有哪些方法呢?

  • 链地址法:在冲突的位置拉一个链表,把冲突的元素放进去。
  • 开放定址法:开放定址法就是从冲突的位置再接着往下找,给冲突元素找个空位。
  • 再哈希法:换种哈希函数,重新计算冲突元素的地址。
  • 建立公共溢出区:再建一个数组,把冲突的元素放进去。

HashMap是线程安全的吗?多线程下会有什么问题?

HashMap线程是不安全的,可能会发生这些问题:

  • 多线程下扩容死循环。JDK1.7中的HashMap使用头插法插入元素,在多线程的环境下,扩容的时候有可能导致环形链表的出现,形成死循环。因此,JDK1.8 使用尾插法插入元素,在扩容时会保持链表元素原本的顺序,不会出现环形链表的问题。
  • 多线程的put可能导致元素的丢失。多线程同时执行put操作,如果计算出来的索引位置是相同的,那会造成前一个key被后一个key覆盖,从而导致元素的丢失。此问题在JDK 1.7 和JDK 1.8中都存在。
  • put 和 get 并发时,可能导致 get 为 null。线程 1 执行 put 时,因为元素个数超出 threshold 而导致rehash,线程 2 此时执行 get,有可能导致这个问题。这个问题在 JDK 1.7 和 JDK 1.8 中都存在。

有什么办法能解决HashMap线程不安全的问题呢?

Java中有HashTable、Collections.synchronizedMap、以及ConcurrentHashMap可以实现线程安全的Map。

  • HashTable 是直接在操作方法上加 synchronized 关键字,锁住整个table数组,粒度比较大;
  • Collections.synchronizedMap 是使用 Collections 集合工具的内部类,通过传入 Map 封装出一个SynchronizedMap对象,内部定义了一个对象锁,方法内通过对象锁实现;
  • ConcurrentHashMap在jdk1.7中使用分段锁,在jdk1.8中使用CAS+synchronized。

能具体说一下ConcurrentHashmap的实现吗?

ConcurrentHashmap线程安全在jdk1.7版本是基于分段锁实现,在jdk1.8是基于CAs+synchronized实现。

jdk1.8中CAS+synchronized

CAS的全称为Compare-And-Swap,直译就是对比交换。是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值。

CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下在旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。

jdk1.8实现线程安全不是在数据结构上下功夫,它的数据结构和HashMap是一样的,数组+链表+红黑树。它实现线程安全的关键点在于put流程。

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值