1.介绍
LinkedList是对一个双向链表的封装,在链表的基础上实现了增、删、改、查等功能,容量也是动态增加,LinkedList继承于AbstractSequentialList类,实现了List接口(可进行队列操作)、Deque 接口(可作为双端队列)、Cloneable(可以克隆)、Serializable序列化接口。
2.使用
1、定义:
LinkedList<Integer> list = new LinkedList<>();
LinkedList<Integer> list1 = new LinkedList<>(list);
构造方法源码:
①、
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();
//数组长度为0就直接返回
int numNew = a.length;
if (numNew == 0)
return false;
//这里pred是插入点的前一个节点,succ是用来保存当前插入位置的节点。
Node<E> pred, succ;
//index==size表示从列表末尾的后一位插
if (index == size) {
//后一位没有值,不需要保存
succ = null;
//插入点的前一个点就是列表末尾的点
pred = last;
} else {
//从其他位置插,succ保存插入点的值
succ = node(index);
//pred指向插入点的前一个点
pred = succ.prev;
}
//对数组中的元素进行遍历
for (Object o : a) {
//获取元素值
@SuppressWarnings("unchecked") E e = (E) o;
//新建一个节点,值为e,prev指向插入点的前一个点
Node<E> newNode = new Node<>(pred, e, null);
//前一个点为空,表示从表头位置插入
if (pred == null)
//这个新节点就是表头
first = newNode;
else
//不是从表头插,插入点的前一个点的next指向这个新节点
pred.next = newNode;
//pred=这个新节点保证每次插入时pred都是插入节点的前一个点
pred = newNode;
}
//所有元素插入完成
//保存的插入位置的点为空
if (succ == null) {
//pred就直接为末尾节点
last = pred;
} else {
//否者将pred指向这个保存的插入位置的节点
pred.next = succ;
succ.prev = pred;
}
//元素个数增加添加的元素个数
size += numNew;
//一致性标志加1
modCount++;
return true;
}
插入的思路:
插入元素有三种情况,第一种从表头的前一个,第二种从表尾的后一个插,其余是从有元素的地方插。另插入位置的点为A,插入位置前一个点为B,新点为N,N2(假设插两个点),插入N时,将N的prev指向B,B的next指向N,A的prev指向N,再插一个N1的话,将N1的prev指向N,N的next指向N1,将A的prev指向N1,将N1的next指向A,这样两个点的插入便实现,再多个点的实现也是一样的。
代码的思路:
新建两个节点,S一个用于保存插入位置的点A,P一个用于插入过程中不停的指向A的前一个点。循环过程中,每一个新点N的prev都指向P,然后P的next也指向N,然后P指向这个新点,所有点都添加完成后,将P的next指向S即可。
2、增加操作
①、.list.add(1);//在列表尾部添加
//添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
//新建个指向列尾的点
final Node<E> l = last;
//新建一个节点,prev指向队尾
final Node<E> newNode = new Node<>(l, e, null);
//队尾指向这个新点
last = newNode;
//如果队尾为空,头部也指向这个点
if (l == null)
first = newNode;
else
//否则之前的队尾点的next指向这个新点
l.next = newNode;
//元素个数加1
size++;
modCount++;
}
②、list.add(3, 0);在索引位置插入点
public void add(int index, E element) {
//判断插入位置是否有效
checkPositionIndex(index);
//如果是在队尾的后一个点插,那就在队尾插入一个新点
if (index == size)
linkLast(element);
else
//在索引处点的前面插入一点
linkBefore(element, node(index));
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//新建一个点指向索引位置的前一个点
final Node<E> pred = succ.prev;
//新建插入点,prev指向pred,next指向索引处的点
final Node<E> newNode = new Node<>(pred, e, succ);
//索引处的点的prev指向新点
succ.prev = newNode;
//pred为空说明在头部前面插点,头部指向这个新点
if (pred == null)
first = newNode;
else
//前一个点的next指向新点
pred.next = newNode;
size++;
modCount++;
}
③、list.addFirst(0);//在列表头添加
public void addFirst(E e) {
linkFirst(e);
}
//思路和上面在某一个点的前面插点差不多
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
④、list.addLast(7);在尾部加
//和add调用了一个方法
public void addLast(E e) {
linkLast(e);
}
3、删除元素
①、list.remove();//删除第一个元素
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
//新建一个指向第二点的点
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
//表头指向第二个点
first = next;
//如果第二个点为空,说明列表只有一个点,令表尾也为空
if (next == null)
last = null;
else
//否则第二个点的prev不指向任何点
next.prev = null;
//元素减1
size--;
modCount++;
return element;
}
②、list.remove(5);//删除固定索引值的元素
public E remove(int index) {
//检查索引值是否有效
checkElementIndex(index);
//将这个点移除
return unlink(node(index));
}
//返回索引处的点
Node<E> node(int index) {
// assert isElementIndex(index);
//通过索引值查找点,如果索引值小于元素数量的一半就从前往后顺序查找
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
//依次往下指向
x = x.next;
//返回点
return x;
//如果索引值大于等于容量的一半就从后往前找
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
//移除一个确定的节点
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;
//不是表头前一个点的next就指向后一个点
} else {
prev.next = next;
x.prev = null;
}
//后一个点为空,就把列尾指向前一个点
if (next == null) {
last = prev;
//否则后一个点的prev指向前一个点
} else {
next.prev = prev;
x.next = null;
}
//清空x
x.item = null;
size--;
modCount++;
return element;
}
③、list.remove((Object)7);//删除第一个与之相同的元素
public boolean remove(Object o) {
//如果这个值是null
if (o == null) {
//从前往后查找到与传入值相同的节点
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
//找到就把这个点删掉
unlink(x);
return true;
}
}
} else {
//有值就用equals,这里equals和==的区别上一篇有介绍
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
④、list.removeFirst();//删除第一个元素
⑤、list.removeLast();//删最后一个元素
public E removeLast() {
final Node<E> l = last;
//最后一个元素为空
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
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;
//否则倒数第二个点的next指向null
else
prev.next = null;
size--;
modCount++;
return element;
}
⑥、list.removeFirstOccurrence((Object)0);//删除第一个与之相同的元素
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
⑦、list.removeLastOccurrence((Object)0);//删除最后一个与之相同的元素
//思路和删第一个一样,只是这里是从后往前找
public boolean removeLastOccurrence(Object o) {
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
⑧、list.clear();//删除列表
//删除列表
public void clear() {
//清空所有节点
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
4、修改元素
list.set(0, 8);//在对应索引设置元素
public E set(int index, E element) {
//判断索引是否有效
checkElementIndex(index);
//获取当前索引的节点
Node<E> x = node(index);
//修改值
E oldVal = x.item;
x.item = element;
return oldVal;
}
5、获取元素
①、list.get(1);获取确定索引值的元素
public E get(int index) {
//判断索引是否有效
checkElementIndex(index);
return node(index).item;
}
②、list.getFirst();获取第一个元素
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
③、list.getLast();获取最后一个元素
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
6、查找元素
①、list.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;
}
②、list.lastIndexOf(0);返回最后一次出现元素的索引
//从后往前找元素
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}
7、遍历
①、迭代器
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
System.out.print(iterator.next());
}
②、foreach
for (Integer integer : list) {
System.out.print(integer);
}
③、for
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i));
}
第三种情况每次都需要通过循环查找节点,这个速度是最慢的,这里写个测试程序来测试:
public static void iteratorGet(LinkedList<Integer> list1){
Iterator<Integer> iterator = list1.iterator();
Integer vInteger = null;
while(iterator.hasNext()){
vInteger = iterator.next();
}
}
public static void foreachGet(LinkedList<Integer> list1){
Integer vInteger = null;
for (Integer integer : list1){
vInteger = integer;
}
}
public static void getMs(LinkedList<Integer> list,int type){
long startMs,endMs,ms;
startMs = System.currentTimeMillis();
switch (type) {
case 1:iteratorGet(list);break;
case 2:foreachGet(list);break;
}
endMs = System.currentTimeMillis();
ms = endMs - startMs;
System.out.println(ms);
}
当数量少时,两个差不多,
当数量超过100000后,foreach的速度快。并且通过测试看出ArrayList的遍历速度快于LinkedList,也好理解,前者是数组,在内存中是连续的,后后者内存不连续,访问肯定会慢。
8、将List转为数组
这里与前面ArrayList是一样的
Integer[] integers = list.toArray(new Integer[0]);
9、做FIF0(先进先出)队列使用
在前面可以看到LinkedList实现了很多添加删除的方法,这些很多操作是用来实现队列操作的:
list.peek();//获取列头的值
list.element();//获取列头的值
list.poll();//列头出列
list.offer(1);//值入列
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
public E element() {
return getFirst();
}
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
public boolean offer(E e) {
return add(e);
}
10、做LIF0(后进先出)的栈使用
list.push(1); //获取将值压到第一个点上
list.pop(); //将第一个点出栈
list.peek(); //获取栈顶的值
public void push(E e) {
addFirst(e);
}
public E pop() {
return removeFirst();
}
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
3、ArrayList和LinkedList对比
1、前者是基于数组实现后者是基于数组实现,后者是双向链表实现。
2、前者是随机访问,后者是顺序访问,遍历速度前者快于后者。
3、前者在内存中是连续的,后者不是连续的。
4、前者的优势是查找速度快,后者的优势是添加和删除快,因为添加和删除的时候前者在内存中要不停的复制,后者不需要。
5、一般情况下查询修改多用前者,增加和删除多用后者。