2.Java集合面试题

67 阅读9分钟

在这里插入图片描述

在这里插入图片描述

1. ArrayList 和 linkedList 的区别

  • ArrayList 底层为数组,Array(数组)是基于索引(index)的数据结构,查询快,查询速度为O(1),删除数据却是开销很大,因为这需要重排数组中的所有数据,(因为删除数据以后,需要把后面所有的数据前移)
  • LinkList底层是一个双链表,在添加和删除元素时具有比ArrayList更好的性能,但是在查询速度慢

2. HashMap和HashTable的区别

  • 两者父类不同

HashMap是继承自AbstractMap类,而Hashtable是继承自Dictionary类。不过它们都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口。

  • 对外提供的接口不同

Hashtable比HashMap多提供了elments() 和contains() 两个方法。elments() 方法继承自Hashtable的父类Dictionnary。elements() 方法用于返回此Hashtable中的value的枚举。

contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上,contansValue() 就只是调用了一下contains() 方法。

  • 对null的支持不同

Hashtable:key和value都不能为null。

HashMap:key可以为null,但是这样的key只能有一个,因为必须保证key的唯一性;可以有多个key 值对应的value为null。

  • 安全性不同

HashMap是线程不安全的,在多线程并发的环境下,可能会产生死锁等问题

Hashtable是线程安全的,它的每个方法上都有synchronized 关键字,因此可直接用于多线程中。

虽然HashMap是线程不安全的,但是它的效率远远高于Hashtable,这样设计是合理的,因为大部分的使用场景都是单线程。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。

ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。

3. Collection和Collections的区别

  • Collection

是集合类的上层接口。本身是一个Interface,里面包含了一些集合的基本操作。

Collection接口时Set接口和List接口的父接口。

  • Collections

Collections是一个集合框架的工具类,里面包含一些对集合的排序,搜索以及序列化的操作。

4. 说说List,Set,Map三者的区别

  • List  (对付顺序的好帮手) : List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的 对象

  • Set (注重独一无二的性质) :不允许重复的集合。不会有多个元素引用相同的对象。

  • Map (用Key来搜索的专) : 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相 同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。

5. Map有什么特点

  • 以键值对存储数据
  • 元素存储循序是无序的
  • 不允许出现重复键

6. 说说ArrayList(数组)

ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数 组的缺点是每个元素之间不能有间隔, 当数组大小不满足时需要增加存储能力,就要将已经有数 组的数据复制到新的存储空间中。 当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进 行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。

7. Vector( 数组实现、 线程安全)

Vector 与 ArrayList 一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一 个线程能够写 Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此, 访问它比访问 ArrayList 慢

8. 说说LinkList(链表)

LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较 慢。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆 栈、队列和双向队列使用。

9. 什么是Set集合

Set 是List集合接口的一个实现类,存储元素不重复,该体系集合用于存储无序(存入和取出的顺序不一定相同)元素, 值不能重复。

10. 说说HashSet( Hash 表)

哈希表边存放的是哈希值。

HashSet 存储元素的顺序并不是按照存入时的顺序(和 List 显然不同) 而是按照哈希值来存的所以取数据也是按照哈希值取得。元素的哈希值是通过元素的hashcode 方法来获取的, HashSet 首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals 方法 如果 equls 结果为 true , HashSet 就视为同一个元素。如果 equals 为 false 就不是同一个元素。

哈希值相同 equals 为 false 的元素是怎么存储呢?就是在同样的哈希值下顺延(可以认为哈希值相 同的元素放在一个哈希桶中)。也就是哈希一样的存一列。

11. 什么是TreeSet(二叉树)

  1. TreeSet()是使用二叉树的原理对新 add()的对象按照指定的顺序排序(升序、降序),每增 加一个对象都会进行排序,将对象插入的二叉树指定的位置。
  2. Integer 和 String 对象都可以进行默认的 TreeSet 排序,而自定义类的对象是不可以的, 自 己定义的类必须实现 Comparable 接口,并且覆写相应的 compareTo()函数,才可以正常使 用。
  3. 在覆写 compare()函数时,要返回相应的值才能使 TreeSet 按照一定的规则来排序
  4. 比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整 数、零或正整数

12. HashMap

12.1 说一下HashMap的底层实现

  • HashMap底层主结构为一个数组,数组中的每个位置存放一个Entry(k,v)
  • JDK7,HashMap由数组+链表构成,在添加元素时,通过hash算法计算entry在数组中的位置,相同hash值得entry对象会组成链表;这种数组+链表在大多数情况下都能有不错得性能,但是极端情况下hash冲突过多,链表长度会很长,使得HashMap的性能急剧下降。此时查询需要遍历全部元素,时间复杂度为O(n)
  • 在JDK8时,为了解决hash冲突导致的链表过长的问题,HashMap改成数组+链表+红黑树;当链表长度大于8时会转表成红黑树。红黑树的查询时间复杂度为O(logn) 在这里插入图片描述

12.2 HashMap中的put()和get()的实现原理

  1. put() 原理

  1. 首先将k,v封装到Node对象当中(节点)。
  2. 然后它的底层会调用K的hashCode()方法得出hash值。
  3. 通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
  1. get() 原理

  1. 先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
  2. 通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。

12.3 HashMap的扩容过程

HashMap在put的时候会先检查当前数组的length,如果插入新的值的时候使得length > 0.75f * size(f 为加载因子,可以在创建hashMap时指定)的话,会将数组进行扩容为当前容量的2倍。 扩容之后必定要将原有hashMap 中的值拷贝到新容量的hashMap 里面,HashMap 默认的容量为16,加载因子为0.75, 也就是说当HashMap 中Entry的个数超过 16 * 0.75 = 12时, 会将容量扩充为 16 * 2 = 32,然后重新计算元素在数组中的位置,这是一个非常耗时的操作,所以我们在使用HashMap的时候如果能预先知道Map中元素的大小,预设其大小能够提升其性能

12.4 HashMap在扩容的时候为什么容量都是原来的2倍,即容量为2^n

HashMap 在计算数组中key的位置时,使用到了hash算法,即对key的hashcode 与当前数组容量 -1 进行与操作(也就是取模运算),容量为2^n时,能够减少hash冲突的几率,从而提高查询效率

13.说说ConcurrentHashMap

ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,整个 ConcurrentHashMap 由一个个 Segment 组成。

在调用put()方法时,会根据hash算法计算出具体存放的Segment,之后对该segment上锁。 Segment 通过继承ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全 。

在Java8时同样对ConcurrentHashMap 引入了红黑树。

14.说说TreeMap(可排序)

TreeMap 实现 SortedMap 接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。 如果使用排序的映射,建议使用 TreeMap。 在使用 TreeMap 时, key 必须实现 Comparable 接口或者在构造 TreeMap 传入自定义的 Comparator,否则会在运行时抛出 java.lang.ClassCastException 类型的异常。