容器面试题

容器概念

什么是集合

集合就是一个存放数据对象引用的容器。
集合类型主要有三种:set、list、map。

set、list、map三者区别

List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。
Set:一个无序(存入和取出顺序有可能不一致)容器,元素不能重复,只允许存入一个null元素。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet(插入无序,可以排序)。
Map是一个键值对集合。 Key无序,唯一;value允许重复。常用的实现类有HashMap、TreeMap、LinkedHashMap、ConcurrentHashMap、HashTable。

集合框架底层数据结构

List

  • Arraylist: 数组
  • Vector: 数组
  • LinkedList: 双向循环链表
    Set
  • HashSet:基于 HashMap 实现的,底层采用 HashMap 来保存元素
  • LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
  • TreeSet: 红黑树(自平衡的排序二叉树。)
    Map
    HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
    LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表。
    HashTable: 数组+链表组成的,链表则是主要为了解决哈希冲突而存在的
    TreeMap: 红黑树(自平衡的排序二叉树)

List

迭代器 Iterator 是什么

Iterator 提供 遍历任何 Collection 的接口。可以从Collection中使用迭代器方法来获取迭代器实例。迭代器允许调用者在迭代过程中移除元素。

List<String> list = new ArrayList<>();
Iterator<String> it = list. iterator();
while(it. hasNext()){
  String obj = it. next();
  System. out. println(obj);
}

Iterator 特点是只能单向遍历,但更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候抛出ConcurrentModificationException异常。

说一下 ArrayList 的优缺点

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

遍历List的方式

for 循环遍历;
迭代器遍历,Iterator 是面向对象的一个设计模式,可以删除元素;
foreach 循环遍历,foreach 内部也是采用了 Iterator 的方式实现,代码简洁,不能删除。
ArrayList 使用for 循环遍历更合适,因为它支持RandomAccess,按位置读取元素的平均时间复杂度为O(1)。

如何实现数组和 List 之间的转换

数组转 List:使用 Arrays.asList(array);
List转数组:使用List 的toArray()方法。

ArrayList 和 LinkedList的区别

底层数据结构:ArrayList 底层是数组实现,LinkedList 底层是双向链表实现。
随机访问效率:ArrayList 比 LinkedList 在随机访问时效率要高,因为LinkedList 底层是链表结构,需要从前往后依此查找。
增加和删除的效率:非首尾的增加和删除操作,LinkedList 比 ArrayList 的效率要高,因为ArrayList 增删操作要影响数组内其他数据的下标。
内存空间占用:LinkedList 比 ArrayList更占内存,因为LinkedList 的节点除了存储数据,还存储了一个指向前一个元素一个指向后一个元素的引用。

多线程下如何使用ArrayList

可以用Collections的synchronizedList 方法把它转换成线程安全的容器后再使用。

为什么 ArrayList 的 elementData 加上 transient

ArrayList 实现了 Serializable 接口是支持序列化的,transient 的作用是不希望 elementData 数组被序列化。因为elementData中有未使用的空间,如果没有使用的空间也序列化会影响性能。ArrayList 自定义了writeObject方法,每次序列化时先序列化ArrayList中非transient元素,然后再遍历elementData,只序列化已存入的元素。这样既加快了序列化的速度,又减小了序列化之后的文件大小。

Map

什么是hash算法

哈希算法是指把任意长度的二进制映射为固定长度的较小的二进制值,这个较小的二进制值叫做哈希值。

什么是链表

链表是一种物理存储单元上不连续的存储结构,元素的逻辑顺序是通过链表中的指针实现的。
链表的优点:
插入删除速度快,因为有指针指向下一个节点,通过改变指针的指向可以方便的增加删除元素;
内存利用率高,可以使用内存中细小的不连续空间(大于node节点的大小);(并且在需要空间时才创建空间)
大小没有固定,扩展灵活。
链表的缺点:
不能随机查找,必须从第一个开始遍历,查找效率低。

HashMap 的实现原理

HashMap底层是数组+链表/红黑树的结构。
HashMap 是基于 hash 算法实现的:
1、当我们 往HashMap中put元素时,利用key的哈希值(hashCode)重新 hash() 计算出当前对象在数组中的下标;(计算下标:(n-1)&hash;n容量,&位与)
2、存储时,如果出现hash值相同的key,若key也是相同的,就覆盖原始值;若key不同,就是出现hash冲突,就把当前的数据放入链表中;如果链表的节点数据超过8个,就转换成红黑树结构。
3、获取数据时,直接找到hash值对应的下标,再进一步判断key是否相同,从而找到对应的值。

HashMap 的扩容是怎么实现的

当HashMap 中的键值对数大于map容量(数组长度)的0.75或者初始化时,就调用resize方法进行扩容;
每次扩容都是扩展2倍;
扩容后元素要么在之前的位置,要么移动到原始位置+旧容量的位置。(扩容时链表拆分为2个链表,红黑树也是拆分)

HashMap put方法的流程

当我们put的时候,首先计算key的hash值计算出下标;如果下标对应的位置没有值,就新建节点添加;如果有值并且key也相同那就直接覆盖,如果key不同就是产生了Hash冲突,如果是红黑树就直接插入数据,链表就判断长度是否大于8,大于8就转换成红黑树插入数据,否则就执行链表的插入操作;期间发行key已存在也是直接覆盖值即可;插入成功后再判断实际存在的数据数量是否超过阈值(capacity的0.75),如果超过就进行扩容。
key的hash值:调用hash()方法获得,hash方法是用key的hashCode()值与key hashCode() 向右移16的值进行异或操作。

如何处理hash冲突

将相同hash值的对象组织成一个链表放在hash值对应的槽位上;如果链表的节点大于8个时就会转换成红黑树。

为什么HashMap中String、Integer 适合作为K

都有用final 关键词修饰是不可变的类,保证key不可更改,不会存在获取hash值不同的情况;
内部也重写了equals()、hashCode()方法,遵守了HashMap内部的规范,不容易出现Hash值计算错误的情况。

HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?

HashMap的容量范围是在16(初始化默认值)~2 ^ 30
答: hashCode() 方法返回的是int整数类型,其范围有32位(为-(2 ^ 31)~(2 ^ 31 - 1)),约有40亿个映射空间,而HashMap实际应用中一般范围要小的多,从而导致通过 hashCode() 计算出的哈希值可能不在数组大小范围内,而无法匹配存储位置;
那怎么解决呢?
HashMap自己实现了 hash() 方法,通过两次扰动让哈希值高低位自行进行异或运算,降低哈希冲突概率也使得数据分布更平均;
使用 hash() 方法运算之后的值来与(数组长度 - 1)的值进行与运算来获取数组下标,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length

HashMap 的长度为什么是2的幂次方

( hash%length==hash&(length-1)的前提是 length 是2的 n 次方)
为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。所以就采用了hash值取余来实现。
“取余(%)操作中如果除数是2的次方就等价于和它除数减一的值进行与(&)操作。 因为采用二进制与操作,相对于直接求余操作能够提高运算效
率,所以 HashMap 的长度就采用了2的幂次方。
那为什么是两次扰动呢?
答:这样就是加大了随机性,使得分布更均匀,减少Hash了冲突。

什么是TreeMap 简介

TreeMap基于红黑树(Red-Black tree)实现。可以根据其键的自然顺序进行排序(或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法)
TreeMap是线程非同步的。

HashMap 和 ConcurrentHashMap 的区别

ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)
HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。

HashMap 与 HashTable 有什么区别

线程安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap );
效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;(如果你要保证线程安全的话就使用 ConcurrentHashMap );
对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,值可以有多个 null。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛NullPointerException。

Set

HashSet、LinkedHashSet、TreeSet 的区别

HashSet:基于 HashMap 实现的,HashSet的值放在了HashMap的key上。不能保证元素的排列顺序,可以有一个null值。非线程安全。
LinkedHashSet: 基于HashSet实现,同时维护了一个双向链表来记录插入的顺序。遍历该集合时,会以元素的添加顺序访问集合的元素。非线程安全。
TreeSet: 红黑树。实现了SortedSet接口,可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序和定制排序,默认自然排序。向TreeSet中加入的得是同一个类的对象。不能有null元素。
三者都保证了元素的唯一性,如果无排序要求可以选用HashSet;如果想取出元素的顺序和放入元素的顺序相同,那么可以选用LinkedHashSet;如果想按照一定规则排序可以选用TreeSet。
自然排序
自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
定制排序
定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法

HashSet的实现原理

HashSet 是基于 HashMap 实现的,HashSet的值存放在HashMap的key上,HashMap的value统一为PRESENT,所以 HashSet的实现比较简单,基本上都是调用HashMap的相关方法来完成。

HashSet 如何检查重复

把对象加入 HashSet 时,HashSet 会先计算对象的hashcode 值来确定对象加入的位置,同时也会与其他已经加入的对象的hashcode 值 作比较,如果没有相符的hashcode,HashSet 会认为对象没有重复出现。但是如果发现相同 hashcode 值的对象,这时会调用 equals() 方法来检查 hashcode 相等的对象是否也相同。如果相同,HashSet 就不会让其加入成功;如果不同,就会重新散列到其他位置。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值