ArrayList、LinkedList、Vector、CopyOnWriteArrayList原理解析
来源:互联网 发布:银行软件开发招聘 编辑:程序博客网 时间:2024/06/06 18:52
一、List家族特点
二、ArrayList
Java中的数组初始化后,长度就是不可变。简单来说,ArrayList就是可变长数组。
由于ArrayList底层是通过数组来实现的,所以必然要解决下面两个问题。
- 初始容量,最大容量
- 如何扩容
ArrayList的初始容量不是在初始化时设置的,而是在第一次进行add后者addAll操作时设置的。(这是JDK1.7做的一个优化,老版本中ArrayList在构造完成后容量默认是10)
下面是ArrayList的构造函数,在初始化时,并没有为数组分配空间
private static final int DEFAULT_CAPACITY = 10;private static final Object[] EMPTY_ELEMENTDATA = {};//存储数据的数组private transient Object[] elementData;public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity];}public ArrayList() { super(); //初始化时,是空数组 this.elementData = EMPTY_ELEMENTDATA;}public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; if (elementData.getClass() != Object[].class) //将elementData拷贝到一个长度为size的新数组 elementData = Arrays.copyOf(elementData, size, Object[].class);}
下面是为ArrayList每次进行add或者addAll操作时分配内存的代码。
private void ensureCapacityInternal(int minCapacity) { //分配的最小容量=max(默认容量10,第一次插入的元素的个数) if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity);}private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code //当数组的长度不够,调用grow方法对数组进行扩容 if (minCapacity - elementData.length > 0) grow(minCapacity);}/** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/** * Increases the capacity to ensure that it can hold at least the number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; //新的容量=以前的容量+以前容量的1/2 int newCapacity = oldCapacity + (oldCapacity >> 1); //如果newCapacity 比minCapacity 还要小,就分配minCapacity //这里存在两种情况,一是newCapacity确实比minCapacity小,二是newCapacity 越界了,超过了int的最大值,导致newCapacity 为负数 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) //newCapacity超过MAX_ARRAY_SIZE时,根据minCapacity来判断是分配MAX_ARRAY_SIZE还是Integer.MAX_VALUE newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity);}private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;}
int newCapacity = oldCapacity + (oldCapacity >> 1);
这句执行后如果超过int的最大值,那么newCapacity会是一个负数。
总结一下ArrayList的扩容算法:
当前容量:oldCapacity
新的容量:newCapacity
此次分配需要的最小容量:minCapacity
当容量不足时,newCapacity=oldCapacity+oldCapacity*1/2,也就是oldCapacity的3/2倍。如果newCapacity发生越界或者newCapacity< minCapacity,那么newCapacity=minCapacity。如果minCapacity>MAX_ARRAY_SIZE,那么最终分配的容量为Integer.MAX_VALUE 或者MAX_ARRAY_SIZE;
三、LinkedList
LinkedList除了实现List接口外,还实现了Deque接口,也就是说,LinkedList除了作为集合外,还可以用作队列。
由于我们讨论的是List家族,所以,下面主要介绍LinkedList作为List中的成员,是如何实现的。
我们都知道,相对于ArrayList,LinkedList有插入更新快、读取慢的特点,这种特性与它底层的数据结构密切相关。
LinkedList底层的数据结构是双向链表。
注:LinkedList最开始的数据结构是双向循环链表(只有一个header指针),后面改成了双向链表(一个first指针、一个last指针)
public class LinkedList<E>{ transient int size = 0; //头节点 transient Node<E> first; //尾节点 transient Node<E> last; private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }}
下面,我们来看看LinkedList插入快,查找慢。
下面是LinkedList插入元素时的代码,可以看出,插入一个元素时newNode时,只需要O(1)的时间复杂度。
public void addLast(E e) { //插入链表尾部 linkLast(e);}void linkLast(E e) { final Node<E> l = last; //新增一个节点,前继为尾节点,后继为null final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++;}
再看看LinkedList在指定位置插入元素的代码,该操作的复杂度主要在node(int index)
方法上,最坏的情况下,会循环size*1/2次,但是相对于ArrayList 的整体后移,还是相当快的。
public void add(int index, E element) { //校验是否越界 checkPositionIndex(index); if (index == size) linkLast(element); else //将element插入node节点 linkBefore(element, node(index));}void linkBefore(E e, Node<E> succ) { // assert succ != null; final Node<E> pred = succ.prev; final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++;}//找到位置index的node节点,如果index<size*1/2,从前往后找,否则从后往前找Node<E> node(int index) { if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; }}
- ArrayList、LinkedList、Vector、CopyOnWriteArrayList原理解析
- JDK源码解析之ArrayList与Vector与CopyOnWriteArrayList
- Vector,ArrayList与LinkedList
- 分清Vector、ArrayList、LinkedList
- ArrayList LinkedList Vector类
- ArrayList LinkedList Vector(转)
- Vector, ArrayList, LinkedList
- ArrayList、Vector和LinkedList
- LinkedList、ArrayList和Vector
- ArrayList Vector LinkedList 区别
- ArrayList、LinkedList、Vector
- LinkedList,ArrayList和Vector
- LinkedList、ArrayList、Vector
- ArrayList Vector LinkedList
- Vector Arraylist LinkedList 区别
- ArrayList、Vector、LinkedList区别
- ArrayList、Vector和LinkedList
- ArrayList、Vector和LinkedList
- TCP socket通信粘包分析及解决思路
- 【MATH】TJOI2015 BZOJ4001 LGP3978 概率论
- 关于SteamVR插件内置的交互系统InteractionSystem
- 手册
- 文章标题
- ArrayList、LinkedList、Vector、CopyOnWriteArrayList原理解析
- C语言局部变量、全局变量、静态局部变量、静态全局变量
- Opencv 基础学习二(对图片进行腐蚀处理、膨胀处理)
- require、include、require_once、include_once区别? 加载区别? 如果程序按需加载某个php文件你如何实现?
- (二).Linux虚拟机的网络设置
- hdoj 1251 统计难题(字典树)
- vue 的data属性
- 顺时针打印矩阵
- Java基础(集合卷)--单列集合老大Collection