一、什么是List
List是一个有序的集合,和set不同的是,List允许存储项的值为空,也允许存储相等值的存储项。
List继承了 Collection的接口,因此包括 Collection提供的各种方法,初次之外还扩展了自己的方法,如下图所示。
二、List的分类
(一)ArrayList
1、什么是ArrayList
ArrayList是一个数组实现的列表,底层是数组,由于数据是存入数组中的,所以它的特点也和数组一样,查询很快,但是中间部分的插入和删除很慢。
2、与Array的区别
- ArrayList内部封装了一个Object类型的数组,所以本质上没有区别,ArrayList的很多方法都是直接在内部数组的基础上调用的Array的方法。但是ArrayList还有其他功能因此更加复杂。
- ArrayList可以存储不同类型的数据,但是Array只能存储相同数据类型的数据。
- Array的长度不可变,ArrayList是变长的,虽然底层是通过扩展数据长度来实现的,但是表面上看是变长的。
- 存取和增删元素
对于一般的引用类型来说,这部分的影响不是很大,但是对于值类型来说,往ArrayList里面添加和修改元素,都会引起装箱和拆箱的操作,频繁的操作可能会影响一部分效率。另外,ArrayList是动态数组,它不包括通过Key或者Value快速访问的算法,所以实际上调用IndexOf、Contains等方法是执行的简单的循环来查找元素,所以频繁的调用此类方法并不比你自己写循环并且稍作优化来的快,如果有这方面的要求,建议使用Hashtable或SortedList等键值对的集合。
3、方法探究
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.默认长度
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to
* DEFAULT_CAPACITY when the first element is added.
*/
//从这里可以看到,ArrayList的底层是由数组实现的,并且默认数组的默认大小是10
private transient Object[] elementData;
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
ArrayList的构造函数
ArrayList有2个构造函数,一个是默认无参的,一个是有参的(数组大小)
如果我们可以预估数组大小,最好使用使用有参构造函数。因为如果使用无参的构造函数,会首先把EMPTY_ELEMENTDATA(默认是10)赋值给elementData
然后根据插入个数于当前数组size比较,不停调用Arrays.copyOf()方法,扩展数组大小造成性能浪费。
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
add操作
//从尾部插入
public boolean add(E e) {
//判断当前数据空间是否够插入数据
ensureCapacityInternal(size + 1); // Increments modCount!!
//将数组后移一位,将新元素赋值给此位置
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
//判断当前数据空间是否够插入数据
ensureCapacityInternal(size + 1); // Increments modCount!!
//用System.arraycopy,把index开始所有数据向后移动一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//将新元素插入到index位置
elementData[index] = element;
size++;
}
private void ensureCapacityInternal(int minCapacity) {
//如果数组为空
if (elementData == EMPTY_ELEMENTDATA) {
//元素长度为默认长度和元素长度取较大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//当元素个数大于数据长度的时候执行此方法
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//>>是移位运算符,相当于int newCapacity = oldCapacity + (oldCapacity/2),但性能会好一些,
int newCapacity = oldCapacity + (oldCapacity >> 1);//根据老数组长度计算新数组长度,默认每次都是自增50%的大小
//下面判断保证新数组元素的个数在正常范围内
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//用新数组继续下面的处理
}
(二)Vector
Vector可以看做是线程安全版的ArrayList,利用synchronized来实现线程安全,所以他的一大弊端就是性能不高,而且这个类太古老已经被弃用。
因此如果我们对线程安全性要求不高就可以使用ArrayList,如果必须保证线程安全可以使用Collections中提供的方法,转变成线程安全的类。
(三)LinkedList
LinkedList底层是一个双向链表。LinkedList继承于AbstractSequentialList,和ArrayList一个套路。内部维护了3个成员变量,一个是当前链表的头节点,一个是尾部节点,还有是链表长度。
方法探究
add(E e)操作
//从指定位置插入(中间插入)
public void add(int index, E element) {
checkPositionIndex(index);
//如果插入位置和size相等相当于尾部插入
if (index == size)
linkLast(element);
else
//否则就是中部插入
linkBefore(element, node(index));
}
//尾部插入
public boolean add(E e) {
//尾部插入的具体实现
linkLast(e);
return true;
}
void linkLast(E e) {
//记录当前最后一个节点
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
//将新增节点赋值给尾节点
last = newNode;
//如果尾节点为空,说明没有元素,那么直接将新增元素赋值给头结点
if (l == null)
first = newNode;
else
原来尾节点的next指向新增的节点
l.next = newNode;
//数量加一
size++;
modCount++;
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//pred为插入节点的指向的前一个位置
final Node<E> pred = succ.prev;
//新增的节点
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
//如果指向前一个为空则说明这个链表为空,将新节点赋给头结点
if (pred == null)
first = newNode;
else
//否则将原来index的pred的next指向新节点
pred.next = newNode;
//数量加1
size++;
modCount++;
}
indexOf操作
从头开始遍历整个链表,如果没有就反-1,有就返回当前下标
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
remove操作
//无参的删除方法默认删除头结点
public E remove() {
return removeFirst();
}
//删除中间节点
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
//删除头结点
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
//x的prev指向x的next
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
//删除头节点非常简单,就是把头节点的值清空,next清空
f.item = null;
f.next = null; // help GC
//把nextNode设为头节点
first = next;
if (next == null)
last = null;
else
//清空next的prev
next.prev = null;
//size减1
size--;
modCount++;
return element;
}
三、总结
List的特性:
- 按顺序查找
- 可以存null值
- 可以存重复的值
对比LinkedList和ArrayList的实现方式不同,可以在不同的场景下使用不同的List 。ArrayList是由数组实现的,方便查找,返回数组下标对应的值即可,适用于多查找的场景 ,LinkedList由链表实现,插入和删除方便,适用于多次数据替换的场景。
ArrayList、LinkedList、Vector的区别和实现原理