深入理解Java集合之List
2018-02-08 21:27
721 查看
List笔录
List相较于set、map,是按照一定顺序存储,List主要分为3类,ArrayList, LinkedList和Vector。以下是List的结构图,本文章重点讲解ArrayList与LinkedList的底层实现原理。ArrayList
ArrayList采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。优点:随机访问元素
缺点:中间插入和移除元素速度较慢。
定义:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable1.AbstractList提供了List接口的默认实现(个别方法为抽象方法)。
2.List接口定义了列表必须实现的方法。
3.RandomAccess是一个标记接口,接口内没有定义任何内容。
4. 实现了Cloneable接口的类,可以调用Object.clone方法返回该对象的浅拷贝。
5. 通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。序列化接口没有方法或字段,仅用于标识可序列化的语义。
底层原理:
1.底层使用数组实现 有两个私有属性:
private transient Object[] elementData;(elementData存储内部元素,Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient)
private int size;(size存储元素数量)
2.构造方法: 1.构造一个默认初始容量为10的空列表。
public ArrayList() { this(10); }
2.构造一个指定初始容量的空列表
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
this.elementData = new Object[initialCapacity];
}
3.构造一个包含指定collection的元素的列表,这些元素按照collection的迭代器返回它们的顺序排列。(返回若不是Object[]将调用Arrays.copyOf方法将其转为Object[])
public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); }
3.存储: ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)这些添加元素的方法。
public boolean add(E e) { ensureCapacity(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
看到add(E e)中先调用了ensureCapacity(size+1)方法,之后将元素的索引赋给elementData[size],而后size自增。例如初次添加时,size为0,add将elementData[0]赋值为e,然后size设置为1(类似执行以下两条语句elementData[0]=e;size=1)。将元素的索引赋给elementData[size]不是会出现数组越界的情况吗?这里关键就在ensureCapacity(size+1)中了。
public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3) / 2 + 1; if (newCapacity < minCapacity) { newCapacity = minCapacity; } // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } }
// 将指定的元素插入此列表中的指定位置。 // 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。 public void add(int index, E element) { if (index > size || index < 0) throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); // 如果数组长度不足,将进行扩容。 ensureCapacity(size + 1); // Increments modCount!! // 将 elementData中从Index位置开始、长度为size-index的元素, // 拷贝到从下标为index+1位置开始的新的elementData数组中。 // 即将当前位于该位置的元素以及所有后续元素右移一个位置。 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacity(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }
先将集合c转换成数组,根据转换后数组的程度和ArrayList的size拓展容量,之后调用System.arraycopy方法复制元素到elementData的尾部,调整size。根据返回的内容分析,只要集合c的大小不为空,即转换后的数组长度不为0则返回true。
public boolean addAll(int index, Collection<? extends E> c) { if (index > size || index < 0) throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); Object[] a = c.toArray(); int numNew = a.length; ensureCapacity(size + numNew); // Increments modCount int numMoved = size - index; if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; }
先判断index是否越界。其他内容与addAll(Collection<? extends E> c)基本一致,只是复制的时候先将index开始的元素向后移动X(c转为数组后的长度)个位置(也是一个复制的过程),之后将数组内容复制到eleme
4000
ntData的index位置至index+X。
// 用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。 public E set(int index, E element) { RangeCheck(index); E oldValue = (E) elementData[index]; elementData[index] = element; return oldValue; }
4.读取
// 返回此列表中指定位置上的元素。 public E get(int index) { RangeCheck(index); return (E) elementData[index]; } private void RangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); }
5.删除
// 移除此列表中指定位置上的元素。 public E remove(int index) { RangeCheck(index); modCount++; E oldValue = (E) elementData[index]; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index + 1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work return oldValue; } // 移除此列表中首次出现的指定元素(如果存在)。这是应为ArrayList中允许存放重复的元素。 public boolean remove(Object o) { // 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。 if (o == null) { for (int index = 0; index < size; index++) { if (elementData[index] == null) { // 类似remove(int index),移除列表中指定位置上的元素。 fastRemove(index); return true; } } } else { for (int index = 0; index < size; index++) { if (o.equals(elementData[index])) { fastRemove(index); return true; } } } return false; }
6.数据清除:
public void clear() { modCount++; // Let gc do its work for (int i = 0; i < size; i++) { elementData[i] = null; } size = 0; }
7.数据复制:
public Object clone() { try { ArrayList<E> v = (ArrayList<E>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } }
8.查找数据是否存在:
public boolean contains(Object o) { return indexOf(o) >= 0; }
9.查询数据坐标:
public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i] == null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; }
10.转化为数组:
public Object[] toArray() { return Arrays.copyOf(elementData, size); }11.public void trimToSize() {
modCount++;
int oldCapacity = elementData.length;
if (size < oldCapacity) {
elementData = Arrays.copyOf(elementData, size);
}
}
将elementData的数组设置为ArrayList实际的容量,动态增长的多余容量被删除了。
LinkedList
优点:中间插入和移除元素代价比较小,优化了顺序访问,在Queue或者栈中应用缺点:随机访问比较慢
定义:
public class LinkedList<E>extends AbstractSequentialList<E>implements List<E>, Deque<E>, Cloneable, java.io.Serializable1.LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
2.LinkedList 实现 List 接口,能对它进行队列操作。
3.LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
4.LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
5.LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
6.LinkedList 是非同步的。
底层原理:LinkedList底层的数据结构是基于双向循环链表的,且头结点中不存放数据
1.私有属性: private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;以下是节点类Entry的实现:
private static class Entry<E> {
E element;
Entry<E> next;
Entry<E> previous;
Entry(E element, Entry<E> next, Entry<E> previous) {
this.element = element;
this.next = next;
this.previous = previous;
}
}
next和previous分别表示该节点的下一个节点跟下一个节点。element是该节点包含的值
2.构造方法: 第一个无参构造方法:
public LinkedList() { header.next = header.previous = header; }
第一个构造方法将header的next跟pervious都指向header,这就是一个双向循环链表的初始化;整个链表就是只有header一个结点,表现为空链表。
第二个接收一个Collection参数c:
public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
先调用第一个构造方法,构造一个空的LinkedList,然后把c通过addAll方法加入进去。
3.增加元素 无论add的哪个实现都需要用到addBefore这个方法,这个方法是私有方法,无法直接从外部程序调用,若需要,只能通过反射。
private Entry<E> addBefore(E e, Entry<E> entry) { Entry<E> newEntry = new Entry<E>(e, entry, entry.previous); newEntry.previous.next = newEntry; newEntry.next.previous = newEntry; size++; modCount++; return newEntry; }
// 将元素(E)添加到LinkedList中 public boolean add(E e) { // 将节点(节点数据是e)添加到表头(header)之前。 // 即,将节点添加到双向链表的末端。 addBefore(e, header); return true; }
传入的是结点数据e,调用addBefore,首先在addBefore()方法内创建一个新的节点newEntry,使newEntry的上一个结点是header.previous,也就是尾部节点,因为这是一个双向循环链表,下一个节点是header,因为新加入的节点需要作为尾节点,作为双向循环链表,尾节点的下一个指向header。因为是双向的,所以需要让周围的节点指向newEntry,然后增加size;
以下的实际增加过程跟上述描述差不多,都是调用了addBefore()方法。
public void add(int index, E element) { addBefore(element, (index == size ? header : entry(index))); } public void addFirst(E e) { addBefore(e, header.next); } public void addLast(E e) { addBefore(e, header); }
4.删除数据:
public E remove(int index) { Entry e = get(index); remove(e); return e.element; }
调用了remove()方法,这个方法同样是私有方法,这就是双向链表删除节点的实现。
private void remove(E e) { if (e == header) throw new NoSuchElementException(); // 将前一节点的next引用赋值为e的下一节点 e.previous.next = e.next; // 将e的下一节点的previous赋值为e的上一节点 e.next.previous = e.previous; // 上面两条语句的执行已经导致了无法在链表中访问到e节点,而下面解除了e节点对前后节点的引用 e.next = e.previous = null; // 将被移除的节点的内容设为null e.element = null; // 修改size大小 size--; }
5.clear元素:
public void clear() { Entry<E> e = header.next; // e可以理解为一个移动的“指针”,因为是循环链表,所以回到header的时候说明已经没有节点了 while (e != header) { // 保留e的下一个节点的引用 Entry<E> next = e.next; // 解除节点e对前后节点的引用 e.next = e.previous = null; // 将节点e的内容置空 e.element = null; // 将e移动到下一个节点 e = next; } // 将header构造成一个循环链表,同构造方法构造一个空的LinkedList header.next = header.previous = header; // 修改size size = 0; modCount++; }
6.获取数据:
public E get(int index) { return entry(index).element; } // 获取双向链表中指定位置的节点 private Entry<E> entry(int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); Entry<E> e = header; // 获取index处的节点。 // 若index < 双向链表长度的1/2(位运算),则从前先后查找; // 否则,从后向前查找。 if (index < (size >> 1)) { for (int i = 0; i <= index; i++) e = e.next; } else { for (int i = size; i > index; i--) e = e.previous; } return e; }
7.查询数据是否存在:
public boolean contains(Object o) { return indexOf(o) != -1; } // 从前向后查找,返回“值为对象(o)的节点对应的索引” 不存在就返回-1 public int indexOf(Object o) { int index = 0; if (o == null) { for (Entry e = header.next; e != header; e = e.next) { if (e.element == null) return index; index++; } } else { for (Entry e = header.next; e != header; e = e.next) { if (o.equals(e.element)) return index; index++; } } return -1; }
8.数据复制:
public Object clone() { LinkedList<E> clone = null; try { clone = (LinkedList<E>) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } clone.header = new Entry<E>(null, null, null); clone.header.next = clone.header.previous = clone.header; clone.size = 0; clone.modCount = 0; for (Entry<E> e = header.next; e != header; e = e.next) clone.add(e.element); return clone; }
调用父类的clone()方法初始化对象链表clone,将clone构造成一个空的双向循环链表,之后将header的下一个节点开始将逐个节点添加到clone中。最后返回克隆的clone对象。
public Object[] toArray() { Object[] result = new Object[size]; int i = 0; for (Entry<E> e = header.next; e != header; e = e.next) { result[i++] = e.element; } return result; }
public <T> T[] toArray(T[] a) { // 先判断出入的数组a的大小是否足够,若大小不够则拓展。 // 这里用到了发射的方法,重新实例化了一个大小为size的数组。 // 之后将数组a赋值给数组result,遍历链表向result中添加的元素。 // 最后判断数组a的长度是否大于size,若大于则将size位置的内容设置为null。返回a*/ if (a.length < size) { a = (T[]) java.lang.reflect.Array.newInstance(a.getClass() .getComponentType(), size); } int i = 0; Object[] result = a; for (Entry<E> e = header.next; e != header; e = e.next) { result[i++] = e.element; } if (a.length > size) { a[size] = null; } return a; }
相关文章推荐
- Java 集合深入理解(4):List<E> 接口
- Java 集合深入理解(4):List<E> 接口
- Java 集合深入理解(11):LinkedList
- Java 集合深入理解(4):List<E> 接口
- Java 集合深入理解(8):AbstractSequentialList
- java集合(Collection接口下的 List、Set 深入理解)
- Java 集合深入理解(8):AbstractSequentialList
- Java 集合深入理解(4):List<E> 接口
- Java 集合深入理解(8):AbstractSequentialList
- Java 集合深入理解(8):AbstractSequentialList
- Java 集合深入理解(8):AbstractSequentialList
- 【深入理解java集合系列】List,Set,Map用法以及区别
- 1.深入理解java集合List
- Java 集合深入理解(15):AbstractMap
- Java 集合深入理解(15):AbstractMap
- java基础之集合框架--LinkedHashMap深入理解
- Java 集合深入理解(14):Map 概述
- Java 集合深入理解(10):Deque 双端队列
- Java 集合深入理解(15):AbstractMap
- Java 集合深入理解(9):Queue 队列