您的位置:首页 > 编程语言 > Java开发

深入理解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.Serializable
 1.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.Serializable
1.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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  List ArrayList LinkedList