一、构造器创建ArrayList
-
构造器
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
-
Object[] EMPTY_ELEMENTDATA = {}; // 长度传0时
-
Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 无参构造
-
当我们已经知道要填充多少个元素到ArrayList中时建议使用指定长度传入,否则最好还是使用无参构造,防止执行多次的扩容等操作损耗性能。
-
为什么不能使用Arrays.asList构建的list来new ArrayList()
-
不是一个同包类
-
创建出来的list既不能删除也不能新增
-
因为ArrayList有一个构造函数是通过Collection类接收参数的,所有Arrays.asList可以创建ArrayList。
public ArrayList(Collection<? extends E> c) { Object[] a = c.toArray(); if ((size = a.length) != 0) { if (c.getClass() == ArrayList.class) { elementData = a; } else { elementData = Arrays.copyOf(a, size, Object[].class); } } else { // replace with empty array. elementData = EMPTY_ELEMENTDATA; } }
-
二、ArrayList的插入操作
-
插入操作就是对数组的操作,就是需要扩容。
-
数组是定长的,如果超过原来的定长长度,扩容则需要申请新的数组长度并把原数组元素拷贝到新数组中。
-
每次扩容都会增加原容量的三分之二。
-
指定位置插入
-
判断size是否可以插入
-
jdk17版本:判断是否已经达到了最大长度,达到了则扩容
-
判断插入后是否需要扩容
-
数据元素迁移,把待插入位置后的元素,顺序往后迁移。
-
给数组的指定位置赋值,也就是把待插入元素插入进来。
-
Jdk17源码
public void add(int index, E element) { rangeCheckForAdd(index); modCount++; final int s; Object[] elementData; if ((s = size) == (elementData = this.elementData).length) elementData = grow(); System.arraycopy(elementData, index, elementData, index + 1, s - index); elementData[index] = element; size = s + 1; }
-
三、LinkedList、ArrayList插入分析
-
两种集合哪一个插入数据的速度更快,需要分情况分析
-
如果是头插入,LinkedList速度会更快,如果是中间插入是ArrayList快一点,如果是尾插入,是ArrayList的速度要更快。
-
-
同样的删除操作因为需要找到具体的位置,如果是删除中间元素的情况下,LinkedList也是很慢的。
-
遍历时如果是LinkedList最好是使用迭代器和foreach,如果使用fori的循环LinkedList的时间复杂度会达到O(n2)
四、双端队列、延迟队列、阻塞队列
-
前置的Stack,被抛弃的栈实现类
-
因为是后进先出且它的底层实现是Vector数组,Vector锁的颗粒度太大,都是直接到方法上的,所以性能并不好。并且使用的是数组结构性能也不好不好。
-
-
双端队列ArrayDeque
-
结构:数组实现,所以会有扩容迁移数据的操作
-
双端队列就是一个环形,所以扩容后继续插入元素也满足后进先出。
-
实现默认容量和HashMap的容量类似,都是以传输量最小的2倍数的一个容量。
-
当头和尾相接,数组则需要两倍的扩容。
-
-
双端队列LinkedList
-
天生支持双端队列,且从头尾取数据时间复杂度也是O(1)。
-
链表和数组的最大区别就是没有扩容和复制的操作,所以LinkedList就是天生的双端队列,但较之ArrayDeque的性能相比,也只是在向头插入的时候快了一些,因为如果是尾插入,ArrayDeque的损耗仅在于扩容操作,而LinkedList则需要不断地创建对象。
-
-
延时队列DelayQueue
-
设定存放时间,依次轮询获取。
-
队列中的元素不会因为存放的先手顺序而导致输出顺序,它们是依赖于休眠时长决定的。
-
DelayQueue时Leader-Followr模式的变种,消费者线程处于等待时,总是等待最先休眠完成的元素。
-
五、其他队列
-
类型
实现
描述
Queue
LinkedBlockingQueue
由链表结构组成的有界阻塞队列
Queue
ArrayBlockingQueue
由数组结构组成的有界阻塞队列
Queue
PriorityBlockingQueue
支持优先级排序的无界阻塞队列
Queue
SynchronousQueue
不存储元素的阻塞队列
Queue
LinkedTransferQueue
由链表结构组成的无界阻塞队列
Deque
LinkedBlockingDeque
由链表结构组成的双向阻塞队列
Deque
ConcurrentLinkedDeque
由链表结构组成的线程安全的双向阻塞队列
机制和队列没有差异,剩下的就是添加了阻塞功能和锁的机制。
六、总结
-
ArrayList的数据结构是什么?
-
基于数组实现,当数组满了的时候就进行扩容操作。
-
-
LinkedList和ArrayList的区别在哪
-
LinkedList是链表实现,ArrayList是数组实现
-
LinkedList的头尾插入速度比较快,中间插入、删除操作比较慢,ArrayList的头插入速度较慢,尾插入速度快,原因在于:LinkedList的头尾插入操作只需要实例化对象并进行绑定操作即可,但中间插入的情况下,会出现O(n2)级别的操作,十分耗时,原因在于它不存在下标,向中间插入的时候需要进行遍历操作再绑定赋值。但如果是ArrayList的中间插入操作,它是数组结构实现,它所带来的损耗最多也就是copy数组和扩容操作的情况会影响它的性能。
-
-
队列有哪几种
-
双端队列,延时队列,阻塞队列
-
双端队列就是头和尾都可以进行弹出,比如LinkedList就是天生的双端队列,它都可以进行头尾的取值。
-
双端队列还有数组结构实现的ArrayDeque
-
-
延时队列的使用场景
-
可以在执行某些任务的时候使用,比如延时删除,延时发送消息等。
-
-
阻塞队列
-