黑马Java热门面试题集合(二)


目录:

(1)集合类中主要有几种接口?

(2)集合类的底层数据结构?

(3)HashSet 如何检查重复?

(4):HashMap 和 HashTable 区别?

(5):ConcurrentHashMap 和 HashTable 区别?

(6)ArrayList 和 linkedList 区别?

(7)HashMap 底层实现原理?

(8)HashMap 什么时候扩容?

(9)HashMap 中的 key 我们可以使用任何类作为 key 吗?

(10)HashMap 的长度为什么是 2 的 N 次方呢?


(1)集合类中主要有几种接口?

–Collection: Collection 是集合 List、 Set、 Queue 的最基本的接口。

–Iterator:迭代器,可以通过迭代器遍历集合中的数据

–Map:是映射表的基础接口

(2)集合类的底层数据结构?

–List

    • Arraylist: Object[] 数组

    • Vector:Object[] 数组

    • LinkedList: 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循 环)

–Set

     • HashSet(无序,唯一): 基于 HashMap 实现的,底层采用 HashMap 来保存元素

     • LinkedHashSet: LinkedHashSet 是 HashSet 的子类,并且其内部是 通过 LinkedHashMap 来实现的。有点类似于我们之前说的 LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有 一点点区别的

     • TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树)

–Queue

    • PriorityQueue: Object[] 数组来实现二叉堆

    • ArrayQueue: Object[] 数组 + 双指针 再来看看 Map 接口下面的集合。 

–Map

     • HashMap: JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉 链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变 化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前 会判断,如果当前数组的长度小于 64,那么会选择先进行数组 扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少 搜索时间

    • LinkedHashMap: LinkedHashMap 继承自 HashMap,所以它的底层 仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外, LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使 得上面的结构可以保持键值对的插入顺序。同时通过对链表进行 相应的操作,实现了访问顺序相关逻辑。

    • Hashtable: 数组+链表组成的,数组是 Hashtable 的主体,链表则 是主要为了解决哈希冲突而存在的

    • TreeMap: 红黑树(自平衡的排序二叉树) 

(3)HashSet 如何检查重复? 

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象 加入的位置,同时也会与其他加入的对象的 hashcode 值作比较,如果没有相 符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是 否真的相同。如果两者相同,HashSet 就不会让加入操作成功。

 (4):HashMap 和 HashTable 区别?

1. 线程是否安全: HashMap 是非线程安全的,Hashtable 是线程安全的, 因为 Hashtable 内部的方法基本都经过 synchronized 修饰。(如果你要 保证线程安全的话就使用 ConcurrentHashMap 吧!);

2. 效率: 因为线程安全的问题,HashMap 要比 Hashtable 效率高一点。 另外,Hashtable 基本被淘汰,不要在代码中使用它;

3. 对 Null key 和 Null value 的支持: HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个; Hashtable 不允许有 null 键和 null 值,否则会抛出 NullPointerException。

4. 初始容量大小和每次扩充容量大小的不同 : ① 创建时如果不指定容 量初始值,Hashtable 默认的初始大小为 11,之后每次扩充,容量变为 原来的 2n+1。HashMap 默认的初始化大小为 16。之后每次扩充,容量 变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小 (HashMap 中的 tableSizeFor()方法保证,下面给出了源代码)。也就是 说 HashMap 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么 是 2 的幂次方。

5. 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大 的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会 判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而 不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。 Hashtable 没有这样的机制。

(5):ConcurrentHashMap 和 HashTable 区别?

ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不 同。

     –底层数据结构: JDK1.7 的 ConcurrentHashMap 底层采用 分段的数组+链 表 实现,JDK1.8 采用的数据结构跟 HashMap1.8 的结构一样,数组+ 链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据 结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表 则是主要为了解决哈希冲突而存在的;

    –实现线程安全的方式(重要):① 在 JDK1.7 的时候,ConcurrentHashMap (分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容 器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在 锁竞争,提高并发访问率。 到了 JDK1.8 的时候已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并 发控制使用 synchronized 和 CAS 来操作。(JDK1.6 以后 对 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全 的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是 已经简化了属性,只是为了兼容旧版本;② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方 法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,

(6)ArrayList 和 linkedList 区别?

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

2. 底层数据结构: Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向 链表和双向循环链表的区别,下面有介绍到!)

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

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

         –LinkedList 采用链表存储,所以,如果是在头尾插入或者删除元素不受元 素位置的影响(add(E e)、addFirst(E e)、addLast(E e)、removeFirst() 、 removeLast()),近似 O(1),如果是要在指定位置 i 插入和删除元素的 话(add(int index, E element),remove(Object o)) 时间复杂度近似为 O(n) ,因为需要先移动到指定位置再插入。

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

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

(7)HashMap 底层实现原理?

–JDK1.8 之前

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

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

–JDK1.8 及以后

相比于之前的版本, JDK1.8 之后在解决哈希冲突时有了较大的变化,当链 表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当 前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红 黑树)时,将链表转化为红黑树,以减少搜索时间。

TreeMap、TreeSet 以及 JDK1.8 之后的 HashMap 底层都用到了红黑树。红 黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会 退化成一个线性结构。

(8)HashMap 什么时候扩容?

当 hashmap 中的元素个数超过数组大小 loadFactor 时,就会进行数组扩容, loadFactor 的默认值为 0.75,也就是说,默认情况下,数组大小为 16,那么 当 hashmap 中元素个数超过 160.75=12 的时候,就把数组的大小扩展为 216=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非 常消耗性能的操作,所以如果我们已经预知 hashmap 中元素的个数,那么预 设元素的个数能够有效的提高 hashmap 的性能。比如说,我们有 1000 个元 素 new HashMap(1000),但是理论上来讲 new HashMap(1024) 更合适,不过上 面 annegu 已经说过,即使是 1000,hashmap 也自动会将其设置为 1024。 但 是 new HashMap(1024) 还不是更合适的,因为 0.751000 < 1000, 也就是说为 了让 0.75 size > 1000, 我们必须这样 new HashMap(2048) 才最合适,既考虑了 & 的问题,也避免了 resize 的问题。

(9)HashMap 中的 key 我们可以使用任何类作为 key 吗?

平时可能大家使用的最多的就是使用 String 作为 HashMap 的 key,但是现在 我们想使用某个自定 义类作为 HashMap 的 key,那就需要注意以下几点:

–如果类重写了 equals 方法,它也应该重写 hashCode 方法。

–类的所有实例需要遵循与 equals 和 hashCode 相关的规则。

–如果一个类没有使用 equals,你不应该在 hashCode 中使用它。

–咱们自定义 key 类的最佳实践是使之为不可变的,这样,hashCode 值可以 被缓存起来,拥有更好的性能。不可变的类也可以确保 hashCode 和 equals 在未来不会改变,这样就会解决与可变相关的问题了。

(10)HashMap 的长度为什么是 2 的 N 次方呢?

为了能让 HashMap 存数据和取数据的效率高,尽可能地减少 hash 值的碰撞, 也就是说尽量把数 据能均匀的分配,每个链表或者红黑树长度尽量相等。 我们首先可能会想到 % 取模的操作来实现。 下面是回答的重点哟: 取余(%)操作中如果除数是 2 的幂次,则等价于与其除数减一的与(&) 操作(也就是说 hash % length == hash &(length - 1) 的前提是 length 是 2 的 n 次方)。并且,采用二进制位操作 & ,相对于 % 能够提高运算效 率。 这就是为什么 HashMap 的长度需要 2 的 N 次方了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

喵俺第一专栏

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

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

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

打赏作者

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

抵扣说明:

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

余额充值