Java集合:Collection和Map

204 阅读10分钟

集合概览

可能会有面试官想先试探一下问:说一说你对Java集合的了解 或者你都了解哪些东西。

这时候要先概述一下:Java常用的集合主要就两大类,一类是Collection。。。一类是Map接口下的集合。。。

image.png

List、Set、Queue、Map四个接口的区别

  • List:元素有序、可重复;

  • Set:元素不可重复,一般情况下,比如HashSet元素存储无序,经过改造的LinkedHashSet和TreeSet元素存储有序

  • Queue:按特定的排序规则来确定顺序。元素有序、可重复

  • Map:键值对存储,key不可重复、无序。value可重复、无序。一般情况下,比如HashMap元素存储无序。经过改造 的比如:LinkedHashMap和TreeMap元素存储有序?

集合框架底层数据结构

需要再详细一点

Collection接口下的集合

List

  • ArrayList:Object[ ]数组

  • Vector:Object[ ]数组

  • LinkedList:双向链表

  • Stack:

Set

  • HashSet:无序、唯一。底层使用HashMap来存储元素

  • LinkedHashSet:HashSet的子类,内部通过LinkedHashMap实现,有序的

  • TreeSet:有序,实现了SortedSet接口,内部通过红黑树来实现

Queue

  • PriorityQueue:Object[ ] 数组来实现小顶堆

  • DelayQueue:PriorityQueue来实现的

  • ArrayDeque: 可扩容动态双向数组。

Map接口下的集合

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

  • LinkedHashMap:继承自HashMap,所以底层跟HashMap一样。但在此基础上,增加了双向链表,使得可以保持插入时的键值对顺序。同时对链表进行相应操作,实现了?

  • Hashtable:数组+链表组成。同HashMap

  • TreeMap:红黑树

List

主要实现类的继承关系图

ArrayList的继承关系图

image.png

ArrayList的常用方法

插入

ArrayList提供的插入元素的方法

  • add():尾部追加,方法返回值boolen
  • add(int index, E element):指定位置插入,方法返回值void
  • addAll(集合):尾部追加一个集合,方法返回值boolean
  • addAll(int index, 集合),指定位置插入一个集合,方法返回值void

删除

ArrayList提供的删除元素的方法

  • remove(int index):删除指定位置的元素,方法返回这个被删除的元素
  • remove(Object o):删除某个元素,一般是删除列表中第一次出现的这个元素,方法返回boolean
  • removeAll(Collection<?> c):删除指定集合里面的所有元素,方法返回boolean
  • removeIf(Predicate<? super E> filter):删除满足一定条件的元素,方法返回boolean
  • removeRange(int fromIndex, int toIndex):删除指定范围内的元素,左闭右开。
  • clear:清空数组中的所有元素。

初始化数组到ArrayList

ArrayList<String> animals = new ArrayList<>(Arrays.asList("Cat""Cow""Dog"));

Arrays.asList的返回值是List类型

其实是用了构造方法:ArrayList(Collection<? extends E> c)

修改

  • set(int index, E element):将指定位置的元素修改为传入的数值

查询

  • get(int index):获取指定位置的元素,方法返回该元素

  • 使用iterator()方法

    class Main {
        public static void main(String[] args){
            ArrayList<String> animals = new ArrayList<>();
    
            //在数组列表中添加元素
            animals.add("Dog");
            animals.add("Cat");
            animals.add("Horse");
            animals.add("Zebra");
    
            //创建一个Iterator对象
            Iterator<String> iterate = animals.iterator();
            System.out.print("ArrayList: ");
    
            //使用Iterator的方法访问元素
            while(iterate.hasNext()){
                System.out.print(iterate.next());
                System.out.print(", ");
            }
        }
    }
    

LinkedList的继承关系图

image.png

LinkedList的常用方法

  • 如果引用类型是LinkedList,可以用的方法是以下两种情况的综合。
  • 如果引用类型是List,可以用的方法就是和ArrayList的一样
  • 如果引用类型的Deque,可以用队列的一些方法,如下
  • 添加
    • addFirst
    • addLast
    • offerFirst
    • offerLast和offer
    • push,栈头加入,
  • 查询
    • get
    • getFirst
    • getLast
    • peekFirst
    • peekLast
  • 查询并删除
    • pollFirst
    • pollLast
    • poll
  • 删除
    • removeFirst
    • removeLast

Vector的继承关系图

image.png

Stack类的继承关系图

image.png

Stack类的常用方法

除了从Vector类继承来的方法,还有以下方法可用:

  • push():元素入栈
  • peek():查询栈顶元素
  • pop():弹出栈顶元素(删除元素并返回该元素)

ArrayList和数组(Array)的区别

  • ArrayList内部也是基于数组实现的,经过一系列的操作,可以实现动态扩容。但是数组创建好之后就不能修改长度了。

  • ArrayList中只能存储对象。对于基本类型,可以使用对应的包装类。数组可以存储对象,也可以存储基本类型。

  • ArrayList提供了很多方法,支持追加元素、插入元素、删除等常见操作。Array是一个固定的数组,只能通过下标访问元素。

  • ArrayList创建时不需要指定大小,代码里默认的初始容量大小是10。Array创建时必须指定数组的大小。

ArrayList和Vector的区别

  • Vector对元素进行操作的各种方法,都加了synchronized关键字,做了线程同步;ArrayList不是线程安全的

  • Vector对元素进行扩容的时候,是增加一倍;ArrayList扩容是增加0.5倍? 目前Vector类已经很少使用了,每个方法都做线程同步,性能比较差。一般业务代码中要做线程安全,都是一系列操作加锁,所以没必要使用Vector。而且现在也有更新的线程安全集合CopyOnWriteArrayList可以使用。

CopyOnWriteArrayList是怎么实现线程安全的

ArrayList插入和删除元素的时间复杂度是多少

插入

  • 头部插入:O(n)
  • 尾部追加:如果容量未超过限制,则O(1);否则要扩容,做一次复制,时间复杂度是O(n)
  • 中间位置插入:O(n)

删除

  • 头部删除:需要将所有元素向前移动一个位置,O(n)
  • 尾部删除:O(1)
  • 指定位置删除:删除之后,需要移动元素,O(n)

LinkedList插入和删除元素的时间复杂度

  • 头部插入/删除:只需要修改头结点的指针即可。O(1)
  • 尾部追加/删除:只需要修改尾部结点的指针即可,O(1)
  • 指定位置插入/删除:需要先遍历链表,定位到指定位置,再修改结点的指针,复杂度O(n)

LinkedList 为什么不能实现 RandomAccess 接口?

RandomAccess 是一个标记接口,用来表明实现该接口的类支持随机访问(即可以通过索引快速访问元素)。由于 LinkedList 底层数据结构是链表,内存地址不连续,只能通过指针来定位,不支持随机快速访问,所以不能实现 RandomAccess 接口。

ArrayList与LinkedList的区别

底层数据结构不同。ArrayList底层是通过Object类型的数组实现的;LinkedList底层是使用的双向链表结构 所以有以下区别

  • 插入和删除元素的时间复杂度不同。见上述两个问题

  • 访问元素的时间复杂度不同。ArrayList可以通过下标,快速获取到元素。但是LinkedList需要遍历所有的结点并找到指定的元素

  • 内存空间占用。ArrayList占用的内存空间通常就是所有元素的大小+一些预留的空间;LinkedList,每个元素都需要多占用一部分空间,要存储前一个结点和后一个结点的指针。

说一说ArrayList的扩容机制

详见文章:ArrayList扩容机制分析

判断是否扩容的流程图

判断是否扩容流程.jpg

总结:

  • 若调用无参的构造方法进行初始化,则在添加第1个元素的时候需要进行一次扩容
  • 添加第11个元素的时候,会进行一次扩容
  • 再往后同理,比较元素数量和当前数组大小,判断是否进行扩容

扩容的步骤

扩容的步骤.jpg

总结:

参考文章JavaGuide:ArrayList的扩容机制分析

Queue

主要实现类的继承关系图

LinkedList的继承关系图

image.png

ArrayDeque的继承关系图

image.png

PriorityQueue的继承关系图

image.png

BlockingQueue(接口,并发)

Queue与Deque的区别

  • 结构上的区别:Queue是单端队列,只能从一端插入元素,另一端删除元素,遵循先进先出的原则;Deque是双端队列,两端都可以插入和删除元素

  • 使用语法上的区别:如下 Queue操作元素的时候,有两种返回值,一种是抛出异常,另一种是返回特殊值

Queue 接口抛出异常返回特殊值
插入队尾,返回booleanadd(E e)offer(E e)
删除队首,成功则返回删除的元素remove()poll()
查询队首元素element()peek(),无值则返回null

Deque 扩展了 Queue 的接口, 增加了在队首和队尾进行插入和删除的方法(Queue中的方法也有),同样根据失败后处理方式的不同分为两类:

Deque 接口抛出异常返回特殊值
插入队首addFirst(E e)offerFirst(E e)
插入队尾addLast(E e)offerLast(E e)
删除队首removeFirst()pollFirst()
删除队尾removeLast()pollLast()
查询队首元素getFirst()peekFirst()
查询队尾元素getLast()peekLast()

事实上,Deque 还提供有 push()pop() 等其他方法,可用于模拟栈。

ArrayDeque与LinkedList的区别

  • ArrayDeque是基于数组和双指针实现的,LinkedList是通过链表实现的
  • ArrayDeque不能存储NULL,LinkedList是支持的
  • ArrayDeque插入元素的时候可能会存在扩容操作,LinkedList不存在这种问题

PriorityQueue

特点:

  • 元素出队顺序,与优先级相关。优先级最高的元素先出队

  • 利用二叉堆的数据结构来实现,底层使用可变长的数组来存储数据

  • 通过堆元素的上浮和下沉,实现了O(logn)时间复杂度内插入和删除堆顶元素

  • 非线程安全,且不支持存储NULL和non-comparable的对象

  • 默认是小顶堆,但可以接收Comparator作为构造参数,来自定义元素优先级的先后。

什么是 BlockingQueue?

ArrayBlockingQueue 和 LinkedBlockingQueue 有什么区别?


以上所有集合涉及的源码分析稍后再写

Set

主要实现类的继承关系图

HashSet类的继承关系图

image.png

TreeSet类的继承关系图

image.png

LinkedHashSet类的继承关系图

image.png

Comparable和Comparator接口的区别

Comparable 接口和 Comparator 接口都是 Java 中用于排序的接口,通常用于对象之间比较大小、排序

  • Comparable接口是java.lang包的一个接口,有一个compareTo(Object obj)方法用来排序

  • Comparator接口是在java.util包,用于排序的方法是compare(Object obj1, Object obj2)

一般需要对一个集合进行自定义排序的时候,需要重写上面两个方法。

无序性和不可重复性的含义是什么

  • 无序性不等于随机性 ,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加 ,而是根据数据的哈希值决定的。

  • 不可重复性是指添加的元素按照 equals() 判断时 ,两个元素不能是相同的。

HashSet、LinkedHashSet、TreeSet的异同

相同点

  • 都实现了Set接口,能保证元素唯一,都不是线程安全的

不同点 底层数据结构不同,所以很多特性也不一样

  • HashSet的底层是基于HashMap实现的,所以元素不重复;添加、删除、查找元素都是O(1)的时间复杂度;元素是无序的。

  • LinkedHashSet是HashSet的子类,有HashSet的性质;初始化时调用父类的构造方法:HashSet(int initialCapacity, float loadFactor, boolean dummy),这个方法基于LinkedHashMap实现,所以又有链表的性质,也就是可以保持元素插入的顺序。

  • TreeSet实现了SortSet的接口,底层是红黑树,所以是有序的;元素也是不重复的;

Map

单独放到一篇文章里说:Java集合:Map面试题

被问过的面试题

  1. java的hashset、sortset的区别

  2. java的hashset、linkedhashset的区别

  3. java有哪些表示数组的集合

  4. 数组和链表的区别

  5. 堆和队列的区别

  6. 常用的哪些类

    答:String、ArrayList和LinkedList

  7. ArrayList和LinkedList的区别

  8. 堆和栈的区别

参考文章

  1. JavaGuide
  2. 菜鸟教程