LinkedList 常用方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 boolean add (E e) void addFirst (E e) addLast (E e) void add (int index, E element) E remove () boolean remove (Object o) E remove (int index) E removeFirst () E removeLast () boolean removeFirstOccurrence (Object o) boolean removeLastOccurrence (Object o) E get (int index) E getFirst () E getLast () -------------------------------------------- boolean offer (E e) boolean offerFirst (E e) boolean offerLast (E e) void push (E e) E pop () E poll () E peek () E peekFirst () E peekLast () --------------------------------------------- int size () ;boolean isEmpty () ;boolean contains (Object o) ;void set (int index,E element) ;List subList (int fromIndex,int toIndex) ;clear();
源码分析
继承于AbstractSequentialList的双向链表:可以被当作堆栈、队列或双端队列进行操作。
实现 List 接口,能进行队列操作。
实现 Deque 接口,能将LinkedList当作双端队列使用1 2 3 public class LinkedList <E > extends AbstractSequentialList <E > implements List <E >, Deque <E >, Cloneable , java .io .Serializable {...}
基于双向链表实现,使用 Node 存储链表节点信息。
1 2 3 4 5 private static class Node <E > { E item; Node<E> next; Node<E> prev; }
每个链表存储了 first 和 last 指针:
1 2 transient Node<E> first;transient Node<E> last;
LinkedList的源码也就是普通的链表操作,没有什么难点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public LinkedList () {} public LinkedList (Collection<? extends E> c) { this (); addAll(c); } public boolean addAll (Collection<? extends E> c) { return addAll(size, c); } public boolean addAll (int index, Collection<? extends E> c) { checkPositionIndex(index); Object[] a = c.toArray(); int numNew = a.length; if (numNew == 0 ) return false ; Node<E> pred, succ; if (index == size) { succ = null ; pred = last; } else { succ = node(index); pred = succ.prev; } for (Object o : a) { @SuppressWarnings ("unchecked" ) E e = (E) o; Node<E> newNode = new Node<>(pred, e, null ); if (pred == null ) first = newNode; else pred.next = newNode; pred = newNode; } if (succ == null ) { last = pred; } else { pred.next = succ; succ.prev = pred; } size += numNew; modCount++; return true ; }
与 ArrayList 的比较
ArrayList 基于动态数组实现,LinkedList 基于双向链表实现;
ArrayList 支持随机访问,LinkedList 不支持;
LinkedList 在任意位置添加删除元素更快
遍历方法及效率 Linkedlist不支持随机访问,所以使用访问next的方式便利更高效。ArrayList使用for随机访问更高效。foreach本质上是使用iterator实现的。
Linkedlist : foreach > iterator >for
ArrayList :for >foreach >iterator
参考:https://www.cnblogs.com/aoguren/p/4771589.html
ArrayList 常用方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ArrayList<String> list = new ArrayList<String>(); ArrayList<Integer> list = new ArrayList<Integer>(7 ); boolean add (Element e) void add (int index, Element e) E remove (int index) E remove (Object o) E get (int index) 获取链表中指定位置处的元素 E set (int index, E element) Object[] toArray () int indexOf (Object o) int lastIndexOf (Object o) boolean contains (Object o) boolean isEmpty () int size () void clear ()
参考:实例讲解ArrayList用法
源码分析
ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
实现了Cloneable 接口,即覆盖了函数 clone(),能被克隆。
实现java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。
实现了 RandomAccess 接口,因此支持随机访问。1 2 public class ArrayList <E > extends AbstractList <E > implements List <E >, RandomAccess , Cloneable , java .io .Serializable {}
构造方法(3种) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private static final int DEFAULT_CAPACITY = 10 ;public ArrayList () { this .elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } 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); } } public ArrayList (Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0 ) { if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this .elementData = EMPTY_ELEMENTDATA; } }
插入数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private void add (E e, Object[] elementData, int s) { if (s == elementData.length) elementData = grow(); elementData[s] = e; size = s + 1 ; } public boolean add (E e) { modCount++; add(e, elementData, size); return true ; } 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 ; }
扩容(重点) 插入数据时没有空间了就进行扩容。
使用 grow() 方法进行扩容,新容量的大小为 oldCapacity + (oldCapacity >> 1) ,也就是旧容量的 1.5 倍。
扩容操作需要调用 Arrays.copyOf() 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private Object[] grow(int minCapacity) { return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity)); } private Object[] grow() { return grow(size + 1 ); } private int newCapacity (int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1 ); if (newCapacity - minCapacity <= 0 ) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) return Math.max(DEFAULT_CAPACITY, minCapacity); if (minCapacity < 0 ) throw new OutOfMemoryError(); return minCapacity; } return (newCapacity - MAX_ARRAY_SIZE <= 0 ) ? newCapacity : hugeCapacity(minCapacity); }
删除数据 需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,该操作的时间复杂度为 O(N),可以看出ArrayList 删除元素的代价是非常高的。
ArrayList源码大量使用了System.arrayCopy()和Arrays.copyOf()方法
copyOf()内部实际上调用了System.arrayCopy()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public E remove (int index) { Objects.checkIndex(index, size); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1 ; if (numMoved > 0 ) System.arraycopy(elementData, index+1 , elementData, index, numMoved); elementData[--size] = null ; return oldValue; } public boolean remove (Object o) { if (o == null ) { for (int index = 0 ; index < size; index++) if (elementData[index] == null ) { fastRemove(index); return true ; } } else { for (int index = 0 ; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true ; } } return false ; } private void fastRemove (int index) { modCount++; int numMoved = size - index - 1 ; if (numMoved > 0 ) System.arraycopy(elementData, index+1 , elementData, index, numMoved); elementData[--size] = null ; }
遍历时删除元素 对集合进行遍历的时候,将集合的大小改变了就会报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ArrayList <Integer> list=new ArrayList(); list.add(10 ); list.add(11 ); list.add(12 ); list.add(13 ); list.add(14 ); list.add(15 ); list.add(16 ); list.add(17 ); for (Integer i: list){ if (i==13 ){ list.remove(i); } } Iterator<Integer>it=list.iterator(); while (it.hasNext()){ Integer num=it.next(); if (num==13 ){ list.remove(num); } } Iterator<Integer>it=list.iterator(); while (it.hasNext()){ Integer num=it.next(); if (num==13 ){ it.remove(num); } }
ensureCapacity方法 大量添加元素之前最好先使用ensureCapacity()方法。可以减少扩容次数,提高效率
1 2 3 4 5 6 7 8 public void ensureCapacity (int minCapacity) { if (minCapacity > elementData.length && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA && minCapacity <= DEFAULT_CAPACITY)) { modCount++; grow(minCapacity); } }
参考:https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/ArrayList-Grow.md
与Vector区别
Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。
Vector是线程安全的
Vector(弃用) 实现与 ArrayList 类似,但是使用了 synchronized 进行同步,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;
替代方案1 使用Collections.synchronizedList(); 得到一个线程安全的 ArrayList。
1 2 List<String> list = new ArrayList<>(); List<String> synList = Collections.synchronizedList(list);
替代方案2 可以使用 java.util.concurrent 并发包下的 CopyOnWriteArrayList 类
1 List<String> list = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList 读写分离
写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。
写操作需要加锁,防止并发写入时导致写入数据丢失。
写操作结束之后需要把原始数组指向新的复制数组。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public boolean add (E e) {final ReentrantLock lock = this .lock;lock.lock(); try {Object[] elements = getArray(); int len = elements.length;Object[] newElements = Arrays.copyOf(elements, len + 1 ); newElements[len] = e; setArray(newElements); return true ;} finally { lock.unlock(); } } final void setArray (Object[] a) {array = a; }
1 2 3 4 @SuppressWarnings ("unchecked" )private E get (Object[] a, int index) { return (E) a[index]; }
适用场景 CopyOnWriteArrayList 在写操作的同时允许读操作,大大提高了读操作的性能,因此很适合读多写少 的应用场景。 但是 CopyOnWriteArrayList 有其缺陷:
内存占用:在写操作时需要复制一个新的数组,使得内存占用为原来的两倍左右;
数据不一致:读操作不能读取实时性的数据,因为部分写操作的数据还未同步到读数组中。
所以 CopyOnWriteArrayList 不适合内存敏感以及对实时性要求很高的场景。
为什么JUC包里没有并发的ArrayList实现 主要原因:很难去开发一个通用并且没有并发瓶颈的线程安全的List。
像ConcurrentHashMap这样的类的真正价值并不是它们保证了线程安全。而在于它们在保证线程安全的同时不存在并发瓶颈。举个例子,ConcurrentHashMap采用了锁分段技术和弱一致性的Map迭代器去规避并发瓶颈。
像ArrayList这样的数据结构,你不知道如何去规避并发的瓶颈。拿contains() 这样一个操作来说,当你进行搜索的时候如何避免锁住整个list?
另一方面,Queue和Deque(基于LinkedList)有并发的实现是因为他们的接口相比List的接口有更多的限制,这些限制使得实现并发成为可能。
CopyOnWriteArrayList是一个有趣的例子,它规避了只读操作(如get/contains)并发的瓶颈,但是它为了做到这点,在修改操作中做了很多工作和修改可见性规则。 此外,修改操作还会锁住整个List,因此这也是一个并发瓶颈。所以从理论上来说,CopyOnWriteArrayList并不算是一个通用的并发List。