一 概述
ArrayList与LinkedList都是对List接口的实现,然而在实现List的过程中实际上是基于数组和链表的区别。
数组:用于处理一组数据类型相同的数据,但是不允许动态定义数组的大小,即在使用数组之前必须确定数组的大小。当实际数据量过少的时候会存在不使用的内存空间,从而造成内存空间的浪费。当然,当数据量过大的时候也会存在超出数组定义的元素个数,造成数组越界。在操作方面,数组插入和删除数据是需要进行数据移动的,效率较低,但是在查找和更新数据的时候因为存在下标索引,所以效率较高。
对于数组分配内存空间,数组元素是保存在内存中的栈区,所以系统会自动申请空间,而且数组元素的内存空间是连续的。
链表:链表是一种可以动态分配内存空间的数据结构,可以适应数据动态变化的情况,而且可以通过指针方便的实现各个数据结点之间的关系,所以可以高效的实现数据的增减。而对于链表中数据的查找,需要从前往后遍历一遍,所以效率比较低。
对于链表分配内存空间,链表数据是保存在内存的堆空间中,每个数据结点的空间需要手动申请,操作比较麻烦。但是链表中的数据空间不是连续存在的。
二 ArrayList
ArrayList类的定义:继承了抽象类AbstractList和实现了RandomAccess,Cloneable和Serializable接口
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
//AbstractList抽象类的定义
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>
//List的接口定义
public interface List<E> extends Collection<E>
//Collection接口的实现类之一Collections中的binarySearch方法
//binarySearch方法源码
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
// instanceof判断对象是否属于某个类或接口类型
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
//binarySearch方法注释
//二分查找
Searches the specified list for the specified object using the binary * search algorithm. The list must be sorted into ascending order * according to the {@linkplain Comparable natural ordering} of its * elements (as by the {@link #sort(List)} method) prior to making this * call. If it is not sorted, the results are undefined. If the list * contains multiple elements equal to the specified object, there is no * guarantee which one will be found.
//方法说明
* <p>This method runs in log(n) time for a "random access" list (which * provides near-constant-time positional access). If the specified list * does not implement the {@link RandomAccess} interface and is large, * this method will do an iterator-based binary search that performs * O(n) link traversals and O(log n) element comparisons. */
根据二分查找说明可知:binarySearch(二分查找),对一个有序序列的查找算法,时间复杂度为O(logn),方法注释中强调二分查找的序列必须为有序序列。
根据方法说明可知:实现RandomAccess接口的List,
满足O(logn)的时间复杂度完成搜索。而未实现RandomAccess接口的List的时间复杂度为O(n)的查找时间复杂度加上O(logn)的比较时间复杂度,即时间复杂为O(nlogn)。
RandomAccess接口:
public interface RandomAccess {
//空的接口
}
RandomAccess是一个Marker(标记接口),只起标记作用,根据binarySearch方法中的(list instanceof RandomAccess)可知,实现RandomAccess接口的List实现二分查找是采用方法Collections.indexedBinarySearch(list, key),否则采用的二分查找方法为Collections.iteratorBinarySearch(list, key);
由源码发现实现RandomAccess接口的List集合采用一般的for循环遍历,而未实现这接口则采用迭代器。
indexedBinarySearch(list,key)(使用for循环效率比较高,使用list.get(mid)),因为ArrayList.get(index)
时间复杂度:O(1),按索引下标查找。
private static <T>
int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
int low = 0;
int high = list.size()-1;
while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = list.get(mid);
int cmp = midVal.compareTo(key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
//Cloneable接口
public interface Cloneable { //空接口 }
Cloneable接口同样为一个标记接口,只有实现了该接口,然后在类中重写Object中的clone方法,并通过类调用clone方法才能古进行克隆。否则会抛出CloneNotSupportedException。
//Serializable接口
public interface Serializable { //空接口 }
Serializable接口同样为标记接口,使得某类可以实现序列化,序列化的目的时将一个实现了Serializable接口的对象转换为字节序列。从而实现不同进程之间的远程通信
三 LinkedList
LinkedList类的定义:继承了抽象类AbstractSequentialList和实现了Deque,Cloneable和Serializable接口
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
未实现RandomAccess接口的实现类使用方法iteratorBinarySearch(list,key)(使用迭代器效率比较高,使用get(i,mid)),因为LinkedList.get(index)
时间复杂度:O(n)
,每次都从链表的第一个数据开始查找。
private static <T>
int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
{
int low = 0;
int high = list.size()-1;
ListIterator<? extends Comparable<? super T>> i = list.listIterator();
while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = get(i, mid);
int cmp = midVal.compareTo(key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
get(i ,mid)(该方法避免链表实现二分查找时每次都从表头开始找,而是根据上次查找的结果继续查找)
/**
* Gets the ith element from the given list by repositioning the specified
* list listIterator.
*/
private static <T> T get(ListIterator<? extends T> i, int index) {
T obj = null;
int pos = i.nextIndex();
if (pos <= index) {
do {
obj = i.next();
} while (pos++ < index);
} else {
do {
obj = i.previous();
} while (--pos > index);
}
return obj;
}
Deque接口:
public interface Deque<E> extends Queue<E> {
//Deque methods
void addFirst(E e);
void addLast(E e);
boolean offerFirst(E e);
boolean offerLast(E e);
E removeFirst();
E removeLast();
E pollFirst();
E pollLast();
E getFirst();
E getLast();
E peekFirst();
E peekLast();
boolean removeFirstOccurrence(Object o);
boolean removeLastOccurrence(Object o);
//Queue methods
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
//Stack methods
void push(E e);
E pop();
//Collection methods
boolean remove(Object o);
boolean contains(Object o);
public int size();
Iterator<E> iterator();
Iterator<E> descendingIterator();
}
Deque为
双端队列。双端队列可以在队列两端都可以进行插入和删除。Deque
是一个比Stack
和Queue
功能更强大的接口,它同时实现了栈和队列的功能。ArrayDeque
和LinkeList
实现了Deque
接口。
Deque既可以作为先进后出的栈数据结构,也可以作为先进先出的队列数据结构。
四 ArrayList与LinkedList的区别
- 数据结构的不同:ArrayList是基于动态数组实现,类同于动态数组,如果没有指定数组的大小,则默认初始化的大小为10的数组,但数据超过10的时候,数组无法保存多余的数据时,系统会另外申请一个长度为当前长度1.5倍的数组,然后会将之前的数据拷贝到新建的数组中。而LinkedList是带头结点和尾结点的双向链表结构,可以作堆栈,队列,双向队列。
- 不同操作的效率不同:当随机访问List的时候,即实现get和set操作,由于ArrayList可以直接使用索引,而LinkedList基于链表实现,需要移动指针进行一次查找。所以ArrayList的效率更高。当删除或者新增数据时,即实现delete和add操作,由于ArrayList需要移动数据所以效率会比较差。
- 数据结构所消耗内存空间的不同:ArrayList主要是存储数据的空间,而LinkedList不仅需要存储数据的空间,而且需要保存结点指针信息的空间。
- 实际的应用场景不同:ArrayList适合频繁查找和更新的应用场景,LinkedList适合频繁插入和删除的应用场景。
- 实现的接口有差别:ArrayList实现标记接口RandomAccess,而LinkedList却没有实现。