Java集合使用方法及应用场景

在这里插入图片描述
Java 集合,也称作容器,主要是由两大接口 (Interface) 派生出来的:CollectionMap
顾名思义,容器就是用来存放数据的。

那么这两大接口的不同之处在于:
Collection 存放单一元素;
Map 存放 key-value 键值对。

Collection

在这里插入图片描述
Collection 里还定义了很多方法,这些方法也都会继承到各个子接口和实现类里,而这些 API 的使用也是日常工作和面试常见常考的,所以我们先来看下这些方法。

操作集合,无非就是「增删改查」四大类,也叫 CRUD:

Create, Read, Update, and Delete.

API 分为四大类:

功能方法
add()/addAll()
remove()/ removeAll()
-
contains()/ containsAll()
其他isEmpty()/size()/toArray()

增:

//add() 方法传入的数据类型必须是 Object,所以当写入基本数据类型的时候,会做自动装箱 auto-boxing 和自动拆箱 unboxing
boolean add(E e);

//addAll(),可以把另一个集合里的元素加到此集合中
boolean addAll(Collection<? extends E> c);

删:

//remove()是删除的指定元素
boolean remove(Object o);

//removeAll(),就是把集合 B 中的所有元素都删掉
boolean removeAll(Collection<?> c);

查:

//查询集合中有没有某个特定的元素
boolean contains(Object o);

//查询集合 A 是否包含了集合 B
boolean containsAll(Collection<?> c);

其他:

//判断集合是否为空
boolean isEmpty();

//获取集合的大小
int size();

//集合转成数组
Object[] toArray();

List

在这里插入图片描述
List:有序,可重复

An ordered collection (also known as a sequence).
Unlike sets, lists typically allow duplicate elements.

List 的实现方式有 LinkedList 和 ArrayList 两种,那么这两个数据结构如何选择。
对于这类选择问题:

一是考虑数据结构是否能完成需要的功能;如果都能完成,二是考虑哪种更高效。

那具体来看这两个 Classes 的 API 和它们的时间复杂度:

功能方法ArrayListLinkedList
add(E e)O(1)O(1)
add(int index, E e)O(n)O(n)
remove(int index)O(n)O(n)
remove(E e)O(n)O(n)
set(int index, E e)O(1)O(n)
get(int index)O(1)O(n)
//末尾上追加元素,虽然 ArrayList 可能会有扩容的情况出现,但是均摊复杂度(amortized time complexity)还是 O(1) 的
add(E e);

//在特定的位置上加元素,LinkedList 需要先找到这个位置,再加上这个元素,虽然单纯的「加」这个动作是 O(1) 的,但是要找到这个位置还是 O(n) 的
add(int index, E e);

//remove 这个 index 上的元素
//ArrayList 找到这个元素的过程是 O(1),但是 remove 之后,后续元素都要往前移动一位,所以均摊复杂度是 O(n)
//LinkedList 也是要先找到这个 index,这个过程是 O(n) 的,所以整体也是 O(n)
remove(int index)

//emove 见到的第一个这个元素
//rrayList 要先找到这个元素,这个过程是 O(n),然后移除后还要往前移一位,这个更是 O(n),总的还是 O(n)
//LinkedList 也是要先找,这个过程是 O(n),然后移走,这个过程是 O(1),总的是 O(n)
remove(E e);

造成时间复杂度的区别的原因主要是?

  • 因为 ArrayList 是用数组来实现的。
  • 而数组和链表的最大区别就是数组是可以随机访问的(random access)。

以上特点造成了在数组里可以通过下标用 O(1) 的时间拿到任何位置的数,而链表则做不到,只能从头开始逐个遍历。
也就是说在「改查」这两个功能上,因为数组能够随机访问,所以 ArrayList 的效率高。

如果是增删的话?
不考虑找到这个元素的时间的情况下

数组因为物理上的连续性,当要增删元素时,在尾部还好,但是其他地方就会导致后续元素都要移动,所以效率较低;而链表则可以轻松的断开和下一个元素的连接,直接插入新元素或者移除旧元素。

但是,实际上不能不考虑找到元素的时间,而且如果是在尾部操作,数据量大时 ArrayList 会更快的。

最终结论

  • 改查选择 ArrayList;
  • 增删在尾部的选择 ArrayList;
  • 其他情况下,如果时间复杂度一样,推荐选择 ArrayList,因为 overhead 更小,或者说内存使用更有效率。

Vector

Vector 继承自 java.util.AbstractList,底层也是用数组来实现的

现在已经被弃用了,因为它加了太多的 synchronized

任何好处都是有代价的,线程安全的成本就是效率低,在某些系统里很容易成为瓶颈,所以现在大家不再在数据结构的层面加 synchronized,而是把这个任务转移给我们程序员

Vector 和 ArrayList 的区别

  • 线程安全问题;
  • 扩容时扩多少的区别。
源码
  • ArrayList 的扩容实现,这个算术右移操作是把这个数的二进制往右移动一位,最左边补符号位,但是因为容量没有负数,所以还是补 0

    右移一位的效果就是除以 2,那么定义的新容量就是原容量的 1.5 倍
    在这里插入图片描述Vector

  • 再来看 Vector的,通常 capacityIncrement 我们并不定义,所以默认情况下它是扩容两倍
    在这里插入图片描述

Queue & Deque

在这里插入图片描述
Queue 是一端进另一端出的线性数据结构;而 Deque 是两端都可以进出的

Queue

Java 中的 这个 Queue 接口稍微有点坑,一般来说队列的语义都是先进先出(FIFO)的。

但是这里有个例外,就是 PriorityQueue,也叫 heap,并不按照进去的时间顺序出来,而是按照规定的优先级出去,并且它的操作并不是 O(1) 的,时间复杂度的计算稍微有点复杂

功能抛异常返回值
add(e)offer(e)
remove()poll()
element()peek()

为什么会抛异常呢?

比如队列空了,那 remove() 就会抛异常,但是 poll() 就返回 null;element() 就会抛异常,而 peek() 就返回 null 就好了

add(e) 怎么会抛异常呢?
有些 Queue 它会有容量的限制,比如 BlockingQueue,那如果已经达到了它最大的容量且不会扩容的,就会抛异常;但如果 offer(e),就会 return false

使用选择:

  • 要用就用同一组 API,前后要统一;
  • 根据需求。如果你需要它抛异常,那就是用抛异常的;不过做算法题时基本不用,所以选那组返回特殊值的就好了

Deque

Deque 是两端都可以进出

功能抛异常返回值
addFirst(e) 或 addLast(e)offerFirst(e) 或 offerLast(e)
removeFirst() 或 removeLast()pollFirst() 或 pollLast()
getFirst() 或 getLast()peekFirst() 或 peekLast()

使用选择:

  • 要用就用同一组 API,前后要统一;
  • 根据需求。如果你需要它抛异常,那就是用抛异常的;不过做算法题时基本不用,所以选那组返回特殊值的就好了

实现类

在这里插入图片描述

  • 如果想实现「普通队列 - 先进先出」的语义,就使用 LinkedList 或者 ArrayDeque 来实现;
  • 如果想实现「优先队列」的语义,就使用 PriorityQueue;
  • 如果想实现「栈」的语义,就使用 ArrayDeque。

在实现普通队列时,如何选择用 LinkedList 还是 ArrayDeque 呢?
推荐使用 ArrayDeque,因为效率高,而 LinkedList 还会有其他的额外开销(overhead)

ArrayDeque 和 LinkedList 的区别有哪些呢?

  • ArrayDeque 是一个可扩容的数组,LinkedList 是链表结构;
  • ArrayDeque 里不可以存 null 值,但是 LinkedList 可以;
  • ArrayDeque 在操作头尾端的增删操作时更高效,但是 LinkedList 只有在当要移除中间某个元素且已经找到了这个元素后的移除才是 O(1) 的;
  • ArrayDeque 在内存使用方面更高效

综合上述,只要不是必须要存 null 值,就选择 ArrayDeque

什么情况下你要选择用 LinkedList 呢?
Java 6 以前,因为 ArrayDeque 在 Java 6 之后才有的,为了版本兼容的问题,实际工作中我们不得不做一些妥协

Stack

Stack 在语义上是 后进先出(LIFO) 的线性数据结构

在 Java 中是怎么实现栈的呢?
虽然 Java 中有 Stack 这个类,但是呢,官方文档都说不让用了!
在这里插入图片描述
原因很简单,因为 Vector 已经过被弃用了,而 Stack 是继承 Vector 的。

如果想实现 Stack 的语义,就用 ArrayDeque :

Deque<Integer> stack = new ArrayDeque<>();

Set

Set:无序,不重复
在这里插入图片描述
Set 的常用实现类:

  • HashSet: 采用 Hashmap 的 key 来储存元素,主要特点是无序的,基本操作都是 O(1) 的时间复杂度。
  • LinkedHashSet: 是一个 HashSet + LinkedList 的结构,特点就是既拥有了 O(1) 的时间复杂度,又能够保留插入的顺序。
  • TreeSet: 采用红黑树结构,特点是可以有序,可以用自然排序或者自定义比较器来排序;缺点就是查询速度没有 HashSet 快。

那每个 Set 的底层实现其实就是对应的 Map:

  • 数值放在 map 中的 key 上,value 上放了个 PRESENT,是一个静态的 Object,相当于 place holder,每个 key 都指向这个 object
    在这里插入图片描述
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值