【面试】集合框架

参考:https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/Java集合框架常见面试题.md

 

1、ArrayList与LinkedList的区别?

1)是否线程安全:ArrayList和LinkedList都不是同步的,也就是不保证线程安全;

2)底层数据结构:ArrayList底层使用的是Object数组;LinkedList底层使用的是双向链表数据结构;

3)插入和删除是否受元素位置的影响:

  • ArrayList采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。比如,执行add(E e)方法的时候,ArrayList会默认将元素追加到列表的末尾,这种情况的时间复杂度为O(1);但是如果要在指定位置 i 插入和删除元素的话(add(int index,E element)),时间复杂度就为O(n-i)。因为在进行上述操作的时候,集合中第 i 位和第 i 个元素之后的(n-i) 个元素都要执行向后/向前 移一位的操作;
  • LinkedList采用链表存储,所以插入、删除元素的时间复杂度不受元素位置的影响,都是近似O(1),而数组为近似O(n)。

4)是否支持快速随机访问:LinkedList不支持高效的随机元素访问,而ArrayList支持。快速随机访问就是通过元素的序号快速获取元素对象(对应 get(int index));

5)内存空间占用:ArrayList的空间浪费主要体现在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(直接前驱+直接后驱+数据)。

List遍历方式选择:

1)实现了RandomAccess接口的list,优先使用普通for循环,其次foreach;

2)未实现RandomAccess接口的list,优先选择 iterator遍历(foreach遍历底层也是通过 iterator实现的),大size的数据,千万不要使用普通的for循环。

补充:双向链表和双向循环链表

 

2、ArrayList与Vector区别?为什么要用ArrayList取代Vector呢?

Vector类的所有方法都是同步的。可以由两个线程安全的访问一个Vector对象,但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。

ArrayList不是同步的,所以在不需要保证线程安全的时候建议使用ArrayList。

 

3、ArrayList的扩容机制?

4.HashMap和HashTable的区别?

1)线程是否安全:HashMap是非线程安全的,HashTable是线程安全的;HashTable内部的方法基本都经过synchronized的修饰。(线程安全可以使用ConcurrentHashMap);

2)效率:因为线程安全的问题,HashMap要比HashTable快;另外,HashTable基本被淘汰,代码中不要使用;

3)对Null key 和Null value的支持:HashMap中Null可以作为键,这样的键只有一个,但可以有一个或多个键所对应的值为null。但在HashTable中put进的键值只要有一个null,直接抛出 NullPointException;

4)初始容量大小和每次扩容大小的不同:

  • 创建时如果不指定容量初始值,HashTable的默认大小为11,之后每次扩容,容量变为原来的2n+1;HashMap默认的初始化大小为16,之后每次扩容,容量变为原来的2倍;
  • 创建时如果给定了容量初始值,,那么HashTable会直接使用你给定的大小;而HashMap会将其扩充为2的幂次方大小,也就说HashMap总是使用2的幂作为Hash表的大小。

5)底层数据结构:JDK1.8以后的HashMap在解决Hash冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜素时间;HashTable没有这样的机制。

5.HashMap与HashSet的区别?

HashSet底层就是基于HashMap实现的。

1)HashMap实现了Map接口;HashSet实现了Set接口;

2)HashMap存储键值对,HashSet仅存储对象;

3)HashMap调用put()方法向Map中添加元素;HashSet调用set()方法向Set中添加元素‘

4)HashMap使用键(key)计算HashCode值;HashSet使用成员对象来计算HashCode的值,对于两个对象来说HashCode可能相同,所以equals()方法来判断对象的相等性。

6.HashMap的底层实现?

JDK1.8之前H是Map底层是数组和链表结合在一起使用,也就是链表散列。HashMap通过key的HashCode经过扰动函数处理后得到hash值,然后通过(n-1)&hash 判断当前元素存放位置(n:数组长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的hash值以及key是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。

所谓扰动函数就是HashMap的hash方法。使用hash方法是为了防止一些实现比较差的hashCode()方法,换句话说,使用扰动函数之后可以减少碰撞。

所谓拉链法就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一个格就是一个链表。若遇到哈希冲突,则将冲突的值加入链表中即可。

JDK1.8之后,相较于之前的版本,JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(8)时,将链表转化为红黑树,以减少搜索时间。

7.HashMap多线程操作导致死循环问题?

参考:https://coolshell.cn/articles/9606.html

8.ConcurrentHashMap和HashTable的区别?

ConcurrntHashMap和HashTable的区别主要体现在 实现线程安全的方式上。

1)底层数据结构:JDK1.7的ConcurrentHashMap底层采用分段数组+链表实现,JDK1.8采用的数据结构跟HashMap1.8一样,数组+链表/红黑二叉树。HashTable采用的是数组+链表的形式,数组是HashMap的主体,链表则是为了解决Hash冲突而存在的;

2)实现线程安全的方式:

  • 在JDK1.7的时候,ConcorrentHashMap(分段锁)对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在所竞争,提高并发访问率。到JDK1.8的时候已经摒弃了Segment概念,而是直接使用node数组+链表+红黑树的数据结构来实现,并发控制使用syncheronized和CAS来操作。整个看起来就像是优化过且线程安全的HashMap;
  • HashTable(同一把锁):使用synchronized来保证线程安全,效率低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用put添加元素,另一个线程不能使用put添加元素,也不能使用get,竞争会越来越激烈效率会越来越低。

9.集合框架底层数据结构总结:

Collection

1)List

  • Array List:Object数组;
  • Vector:Object数组;
  • LinkedList:双向链表(JDK1.7之前是循环双向链表);

2)Set

  • HashSet(无序,唯一):基于HashMap实现的,底层采用HashMap来保存元素;
  • LinkedHashSet:LinkedHashSet继承自HashSet,并且其内部是通过LinkedHashMap来实现的。有点类似于我们之前说的LinkedHashMap;
  • TreeSet(有序,唯一):红黑树;

3)Map

  • HashMap:JDK1.8之前HashMap由数组+链表组成,数组是HashMap的主体,链表则是为了解决Hash冲突而存在的。JDK1.8之后在解决Hash冲突时有了较大的变化,当链表阈值大于8时,将链表转化为红黑树,以减少搜索时间;
  • LinkedHashMap:LinkedHashMap继承自HashMap,所以它的底层与HashMap相同。另外,LinkedHashMap在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑;
  • HashTable:数组+链表组成,数组是hashMap的主体,链表则是为了解决哈希冲突而存在的;
  • TreeMap:红黑树。

10.如何选用集合?

主要根据集合的特点来选用,比如我们需要根据键值获取元素值时就选用Map接口下的集合;需要排序时就选择TreeMap,不需要排序就选择HashMap,需要保证线程安全就选用ConcurrentHashMap;

当我们只需要存放元素值时,就选择实现Collection接口的集合,需要保证元素唯一时选择实现Set接口的集合,比如TreeSet或HashSet;

不需要就选择实现List接口的集合,比如ArrayList或LinkedList。

 

猜你喜欢

转载自www.cnblogs.com/Rain1203/p/11245974.html