Java小技巧,Java 集合看这一篇就够了


boolean add(E e); 

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

还有另外一个方法 addAll(),可以把另一个集合里的元素加到此集合中。


boolean addAll(Collection<? extends E> c); 

删:


boolean remove(Object o); 

remove()是删除的指定元素。

那和 addAll() 对应的,

自然就有removeAll(),就是把集合 B 中的所有元素都删掉。


boolean removeAll(Collection<?> c); 

改:

Collection Interface 里并没有直接改元素的操作,反正删和增就可以完成改了嘛!

查:

查下集合中有没有某个特定的元素:


boolean contains(Object o); 

查集合 A 是否包含了集合 B:


boolean containsAll(Collection<?> c); 

还有一些对集合整体的操作:

判断集合是否为空:


boolean isEmpty(); 

集合的大小:


int size(); 

把集合转成数组:


Object[] toArray(); 

以上就是 Collection 中常用的 API 了。

在接口里都定义好了,子类不要也得要。

当然子类也会做一些自己的实现,这样就有了不同的数据结构。

那我们一个个来看。

List


在这里插入图片描述

List 最大的特点就是:有序,可重复。

看官网说的:

An ordered collection (also known as a sequence).

Unlike sets, lists typically allow duplicate elements.

这一下把 Set 的特点也说出来了,和 List 完全相反,Set 是 无序,不重复的。

List 的实现方式有 LinkedList 和 ArrayList 两种,那面试时最常问的就是这两个数据结构如何选择。

对于这类选择问题:

一是考虑数据结构是否能完成需要的功能;

如果都能完成,二是考虑哪种更高效。

(万事都是如此啊。)

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

在这里插入图片描述

稍微解释几个:

add(E e) 是在尾巴上加元素,虽然 ArrayList 可能会有扩容的情况出现,但是均摊复杂度(amortized time complexity)还是 O(1) 的。

add(int index, E e)是在特定的位置上加元素,LinkedList 需要先找到这个位置,再加上这个元素,虽然单纯的「加」这个动作是 O(1) 的,但是要找到这个位置还是 O(n) 的。(这个有的人就认为是 O(1),和面试官解释清楚就行了,拒绝扛精。

remove(int index)是 remove 这个 index 上的元素,所以

  • ArrayList 找到这个元素的过程是 O(1),但是 remove 之后,后续元素都要往前移动一位,所以均摊复杂度是 O(n);

  • LinkedList 也是要先找到这个 index,这个过程是 O(n) 的,所以整体也是 O(n)。

remove(E e)是 remove 见到的第一个这个元素,那么

  • ArrayList 要先找到这个元素,这个过程是 O(n),然后移除后还要往前移一位,这个更是 O(n),总的还是 O(n);

  • LinkedList 也是要先找,这个过程是 O(n),然后移走,这个过程是 O(1),总的是 O(n).

那造成时间复杂度的区别的原因是什么呢?

答:

  • 因为 ArrayList 是用数组来实现的。

  • 而数组和链表的最大区别就是数组是可以随机访问的(random access)

这个特点造成了在数组里可以通过下标用 O(1) 的时间拿到任何位置的数,而链表则做不到,只能从头开始逐个遍历。

也就是说在「改查」这两个功能上,因为数组能够随机访问,所以 ArrayList 的效率高。

那「增删」呢?

如果不考虑找到这个元素的时间,

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

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

所以说:

1.改查选择 ArrayList;

2.增删在尾部的选择 ArrayList;

3.其他情况下,如果时间复杂度一样,推荐选择 ArrayList,因为 overhead 更小,或者说内存使用更有效率。

Vector


那作为 List 的最后一个知识点,我们来聊一下 Vector。这也是一个年龄暴露帖,用过的都是大佬。

那 Vector 和 ArrayList 一样,也是继承自 java.util.AbstractList,底层也是用数组来实现的。

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

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

那么面试常问题:Vector 和 ArrayList 的区别是什么,只答出来这个还还不太全面。

来看 stack overflow 上的高票回答:

在这里插入图片描述

一是刚才已经说过的线程安全问题;

二是扩容时扩多少的区别。

这个得看看源码:

在这里插入图片描述

这是 ArrayList 的扩容实现,这个算术右移操作是把这个数的二进制往右移动一位,最左边补符号位,但是因为容量没有负数,所以还是补 0.

那右移一位的效果就是除以 2,那么定义的新容量就是原容量的 1.5 倍。

不了解这个右移操作符的小伙伴,公众号内回复「二进制」快复习一下吧~

再来看 Vector 的:

在这里插入图片描述

因为通常 capacityIncrement 我们并不定义,所以默认情况下它是扩容两倍。

答出来这两点,就肯定没问题了。

Queue & Deque


Queue 是一端进另一端出的线性数据结构;而 Deque 是两端都可以进出的。

在这里插入图片描述

Queue


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

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

那 Queue 的方法官网都总结好了,它有两组 API,基本功能是一样的,但是呢:

  • 一组是会抛异常的;

  • 另一组会返回一个特殊值。

    在这里插入图片描述

为什么会抛异常呢?

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

那 add(e) 怎么会抛异常呢?

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

Deque


Deque 是两端都可以进出的,那自然是有针对 First 端的操作和对 Last 端的操作,那每端都有两组,一组抛异常,一组返回特殊值:

在这里插入图片描述

使用时同理,要用就用同一组。

Queue 和 Deque 的这些 API 都是 O(1) 的时间复杂度,准确来说是均摊时间复杂度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值