Java面试总结——集合篇

                                               摘自javaguide的集合总体框架图:        

Java面试总结——集合篇_第1张图片


一、单列集合汇总 

List, Set, Queue, Map 的区别

  • List:底层基于object[]数组,存储的元素有序、可重复。

  • Set:底层基于HashMap实现,存储的元素无序,不可重复。

  • Queue:单端队列,存储的元素有序、可重复。

  • Map:使用键值对(key-value)存储,key 是无序的、不可重复的。

HashSet、LinkedHashSet 和 TreeSet 的异同

  • 三者都是Set 接口的实现类,都能保证元素唯一,并且都不是线程安全的。
  • 三者主要区别在于底层实现的数据结构不同:
    • HashSet底层基于哈希表,元素具有唯一性。
    • LinkedHashSet底层基于链表和哈希表,元素具有唯一性和有序性(元素顺序满足FIFO)
    • TreeSet底层基于红黑树,支持对元素自定义排序规则。

ArrayList 和 Array(数组)的区别?

  • Array的大小固定;ArrayList可以动态扩容。
  • ArrayList允许使用泛型确保类型安全;Array不行。
  • ArrayList具备基本的增删改查操作;Array只能下标进行查询,没有动态增删改元素的能力。
  • Array既可以存储基本数据类型也可以存储对象;ArrayList只能存储对象,对于基本数据类型,需要将其转化为对应的包装类。

ArrayList 与 LinkedList 区别?

  • 底层数据结构:ArrayList底层使用object[]数组,LinkedList底层使用双向链表。
  • 是否支持快速随机访问:ArrayList支持,LinkedList不支持。
  • 插入和删除是否受元素位置的影响:
    • ArrayList添加元素时默认添加至列表尾部,此时时间复杂度为O(1);但如果在指定位置添加和删除元素时,时间复杂度为O(n)。
    • LinkedList在头尾插入和删除时时间复杂度为O(1);但如果在指定位置插入删除元素时间复杂度为O(n)。

ArrayList扩容机制

ArrayList三个构造函数:

  • ArrayList() 默认创建长度为0的数组。

  • ArrayList(int initialCapacity) 创建指定容量的数组。

  • ArrayList(Collection c) 使用集合c的大小作为数组容量。

        首次向集合中 add 单个元素时,集合扩容为10,再次扩容为上次的1.5倍。(扩容使用的是位运算,奇数*1.5向下取整)。

        首次向集合a中 addAll 集合b的元素时,集合a扩容为max(10,集合b的元素个数) ,接着再向集合a中 addAll 集合c的元素时,集合a扩容为max(集合a容量的1.5倍,集合c的元素个数)。

        位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍:

int newCapacity = oldCapacity + (oldCapacity >> 1);

ArrayList扩容机制描述:

        ArrayList是一个数组结构的存储容器,默认数组长度是10,也可以在初始构建的时候指定长度。随着不断地向容器中添加元素,当达到上限时,ArrayList会自动进行扩容,扩容流程如下:首先会创建一个新的数组,长度为原始数组的1.5倍(使用位运算),然后使用Arrays.copy方法将老数组的元素copy到新数组中,再将需要添加的新元素添加到新数组中

Comparable 和 Comparator 的区别

        Comparable和Comparator都是接口,都可以用来进行比较、排序,可以将Comparable理解为“内部比较器”,Comparator理解为“外部比较器”

  • 实现方式
    • Comparable可以直接在需要进行排序的实体类中实现,重写compateTo方法即可。
    • Comparator需要另外创建一个实现Comparator接口的实现类来作为“比较器”,并在排序时将比较器作为参数传入。
  • 各自的优缺点
    • Comparable 实现比较简单,但是需要修改源代码。
    • Comparator需要新建比较器类,较为复杂,但是不需要修改源代码,并且新建的比较器类可以供多个对象排序使用。

具体参考这篇文章 Comparable和Comparator区别


 二、双列集合汇总

为什么要使用HashMap,底层原理是什么?

        HashMap是一个集合了查询效率和增删效率的容器,内部存的都是一个个键值对,可以通过访问键值对其进行访问和修改。

  • jdk1.8以前,HashMap底层是数组+链表的结构,当发生哈希冲突时,会采用头插法将元素插入链表中。
  • jdk1.8之后,HashMap底层是数组+链表+红黑树的结构,发生哈希冲突时采用尾插法将元素插入链表中,并且当链表长度大于阈值(默认为8)时,会将链表改为红黑树进行存储。

HashMap 中 hash 值的作用?

        HashMap 中的 hash 值是由hash函数产生的,所有键值对存放的位置都是由 hash值和(length-1) 与运算得到的(length必须为2的幂次方,此时与运算等价于对length-1取模)。因此,简单来说hash值就是用来定位某键值对在HashMap中存放的位置的

HashMap 中的数组长度为什么必须是 2 的幂次方?

        length为2的幂次方可以确保(length-1)的二进制低位都是1,此时hash&(length-1) 等价于 hash%(length-1) ,并且位运算的效率较高。

JDK 1.7中HashMap 为什么会形成死循环?

        JDK 1.7中在链表中添加元素的方式是头插法当两个线程同时对HashMap进行扩容操作时,可能会形成环形链表,产生死循环。JDK 1.7中采用了尾插法来避免链表导致,从而避免产生环形链表。

        具体来说:HashMap扩容时,会将旧HashMap的数据移植到 扩容的新HashMap中,而由于链表的插入方式是头插法,a->b->c 会变成 c->b->a ,旧线程仍然认为a节点后面是b,而b节点后面已经是a了,这里就会产生死循环。

HashMap和Hashtable的区别

  • 线程安全与效率
    • HashMap线程不安全,但效率相对较高。
    • Hashtable线程安全,效率相对较低。
  • 对 Null 的支持:
    • HashMap可以存储null值,键只能存一个(对应的key为0),值可以存多个。
    • Hashtable不可以存储null键和null值。
  • 初始容量大小和每次扩容大小:
    • 不指定容量:
      • HashMap默认初始化大小为 16,之后每次扩充,容量变为原来的 2 倍。
      • Hashtable默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。
    • 指定容量
      • HashMap会将指定容量扩充为 2 的幂次方大小,即HashMap总是使用 2 的幂作为哈希表的大小。
      • Hashtable直接使用指定的容量。
  • 扩容方式不同:当容量不足时要进行resize方法,而resize有两个步骤:
    • ①扩容:两者扩容大小不一样。
    • ②rehash:两者都会重新计算hash值,而两者计算hash的值的方式也不同。(如下代码)
//hashMap计算hash值
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
//而hashtable直接使用hashcode值作为最终的hash值
  • 底层数据结构:
    • JDK1.8 以后的 HashMap在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时 ,将链表转化为红黑树以减少搜索时间。
    • Hashtable只使用链表解决哈希冲突。

HashSet 如何检查重复

        当我们将对象加入HashSet时,HashSet会计算该对象的hashcode值,并与HashSet中其他对象作比较:若没有hashcode相同的对象,则该对象不重复,允许加入;若有hashcode相同的对象,还需要使用equals()方法检查两对象是否真的相同,如果相同则不允许加入该对象。

ps:

  • hashcode是某个对象的哈希值,相同对象的hashcode值一定相同,不同对象的hashcode值也有可能相同(即哈希冲突)。

你可能感兴趣的:(Java八股,java,开发语言,集合,面试)