2.Java数据结构原理解析-List系列
2017-11-21 22:23
621 查看
一、List家族特点
集合 | 效率 | 线程安全性 |
---|---|---|
ArrayList | 读取快,插入慢 | 线程不安全 |
LinkedList | 插入快,读取慢 | 线程不安全 |
Vector | 慢 | 线程安全 |
CopyOnWriteArrayList | 读取快,插入慢 | 线程安全 |
二、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; } }
四、CopyOnWriteArrayList
CopyOnWriteArrayList在JDBC的DriverManager中有使用,用于保存所有注册的驱动。相关文章推荐
- 3.Java数据结构原理解析-Queue系列
- 1.Java数据结构原理解析-Map系列
- 4.Java数据结构原理解析-Set系列
- java 中的JDK封装的数据结构和算法解析(集合类)----链表 List 之 Vector (向量)
- Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
- Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
- java基础解析系列(六)---注解原理及使用
- Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
- Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
- Java的数据结构相关的类实现原理,比如LinkedList,ArrayList,HashMap,TreeMap
- Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
- java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现
- Java中LinkedList原理代码解析
- Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
- java基础解析系列(七)---ThreadLocal原理分析
- Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
- 深入Java集合学习系列:LinkedList的实现原理
- java 中的JDK封装的数据结构和算法解析(集合类)----顺序表 List 之 ArrayList
- java基础解析系列(八)---fail-fast机制及CopyOnWriteArrayList的原理
- java 中的JDK封装的数据结构和算法解析(集合类)----链表 List 之 LinkedList