java集合类之LinkedList详解

java

由于LinkedList是一个实现了Deque的双端队列,所以LinkedList既可以当做Queue,又可以当做Stack,在将LinkedList当做Stack时,使用pop()、push()、peek()方法需要注意的是LinkedList内部是将链表头部当做栈顶,链表尾部当做栈底

LinkedList是一个双向链表,没有初始化大小,也没有扩容机制,就是一直在前面或者后面新增就好

特点:随机访问慢、插入删除速度快

二、源码分析

由于LinkedList实现了List和Deque两个接口,所以LinkedList方法分两种,一种是List接口的方法,第二种是Deque接口的方法

1、全局变量

(1)当前链表中的数据个数

 transient int size = 0;

(2)指向链表头部

 transient Node<E> first;

(3)指向链表尾部

 transient Node<E> last;

2、构造函数

(1)不带参数的构造方法

 public LinkedList() {

}

(2)带collection参数的构造方法

 public LinkedList(Collection<? extends E> c) {

this();

addAll(c);//

}

说明:当使用第二个构造方法时,会调用addAll()方法将集合中的元素添加到链表中

3、方法

(1)LinkLast(E e)

 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//链表不为空,那么将该结点作为原链表尾部的后继节点

l.next = newNode;

size++;//增加尺寸

modCount++;

}

说明:LinkLast方法就是一个链表尾部添加一个双端节点的操作,但是需要注意对链表为空时头节点的处理

(2) node(ine index)(根据索引index返回node节点)

 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;

}}

(3)linkBefore(E e, Node< E > succ)

 void linkBefore(E e, Node<E> succ) {

// assert succ != null;

final Node<E> pred = succ.prev;

final Node<E> newNode = new Node<>(pred, e, succ);

succ.prev = newNode;

if (pred == null)

first = newNode;

else

pred.next = newNode;

size++;

modCount++;}

(4)linkFirst(E e)

 private void linkFirst(E e) {

final Node<E> f = first;

final Node<E> newNode = new Node<>(null, e, f);//新建节点,以头节点为后继节点

first = newNode;

//如果链表为空,last节点也指向该节点

if (f == null)

last = newNode;

//否则,将头节点的前驱指针指向新节点

else

f.prev = newNode;

size++;

modCount++;}

说明:

  1. 创建newNode节点,将newNode的后继指针指向succ,前驱指针指向pred
  2. 将succ的前驱指针指向newNode
  3. 根据pred是否为null,进行不同操作。

    • 如果pred为null,说明该节点插入在头节点之前,要重置first头节点
    • 如果pred不为null,那么直接将pred的后继指针指向newNode即可

(5)unlink(Node< E > 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;如果删除的节点是头节点,令头节点指向该节点的后继节点

} 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;

}

(6)unlinkFirst(Node 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

next.prev = null;

size--;

modCount++;

return element;

}

(7)unlinkLast(Node< E > 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;

else

prev.next = null;

size--;

modCount++;

return element;

}

(1)List接口的添加操作

add(E e)用于将元素添加到链表尾部

 public boolean add(E e) {

linkLast(e);

return true;

}

add(int index,E e)用于在指定位置添加元素

 public void add(int index, E element) {

checkPositionIndex(index);

if (index == size)

linkLast(element);

else

linkBefore(element, node(index));

}

说明:1、检查index的范围,否则抛出异常 2、如果插入位置是链表尾部,那么调用LindLast方法 3、如果插入位置是链表中间,那么调用linkBefore方法

addAll有两个方法,一个参数的方法表示将集合元素添加到链表尾部;而两个参数的方法指定了开始插入的位置。

 //将集合插入到链表尾部,即开始索引位置为size

public boolean addAll(Collection<? extends E> c) {

return addAll(size, c);

}

//将集合从指定位置开始插入

public boolean addAll(int index, Collection< ? extends E> c) {

//Step 1:检查index范围

checkPositionIndex(index);

//Step 2:得到集合的数据

Object[] a = c.toArray();

int numNew = a.length;

if (numNew == 0)

return false;

//Step 3:得到插入位置的前驱节点和后继节点

Node<E> pred, succ;

//如果插入位置为尾部,前驱节点为last,后继节点为null

if (index == size) {

succ = null;

pred = last;

}

//否则,调用node()方法得到后继节点,再得到前驱节点

else {

succ = node(index);

pred = succ.prev;

}

//Step 4:遍历数据将数据插入

for (Object o : a) {

@SuppressWarnings("unchecked") E e = (E) o;

//创建新节点

Node<E> newNode = new Node<>(pred, e, null);

//如果插入位置在链表头部

if (pred == null)

first = newNode;

else

pred.next = newNode;

pred = newNode;

}

//如果插入位置在尾部,重置last节点

if (succ == null) {

last = pred;

}

//否则,将插入的链表与先前链表连接起来

else {

pred.next = succ;

succ.prev = pred;

}

size += numNew;

modCount++;

return true;}

说明:1. 检查index索引范围

2. 得到集合数据

3. 得到插入位置的前驱和后继节点

4. 遍历数据,将数据插入到指定位置

(2)Deque接口的添加操作

push(E e)

    public void push(E e) {

addFirst(e);

}

说明:push(E e)方法用于将元素添加到链表头部

addFirst(E e)

 public void addFirst(E e) {

linkFirst(e);}

说明:addFirst(E e)方法用于将元素添加到链表头部

addLast(E e)

 public void addLast(E e) {

linkLast(e);}

说明:addLast()方法用于将元素添加到链表尾部,与add方法实现一样,只不过return有区别

offer(E e)

 public boolean offer(E e) {

return add(e);}

说明:此方法用于将数据添加到链表尾部,其内部调用了add(E e)方法

offerFirst(E e)

 public boolean offerFirst(E e) {

addFirst(e);

return true;}

说明:此方法用于将数据插入链表头部,与addFirst区别在于该方法返回特定的返回值,而addFirst返回值为void

offerLast(E e)

 public boolean offerLast(E e) {

addLast(e);

return true;}

说明:offerLast()与addLast()的区别和offerFirst()和addFirst()的区别一样

(3)根据位置取数据

获取任意位置的get(int index)方法

 public E get(int index) {

//检查边界

checkElementIndex(index);

return node(index).item;}

说明:get(int index)方法根据指定索引返回数据,如果索引越界,那么会抛出异常

获取位置为0的头节点数据

LinkedList中有多种方法可以获得头节点的数据,实现大同小异,区别在于对链表为空时的处理,是抛出异常还是返回null

其中getFirst()和element()方法将会在链表为空时,抛出异常:

 public E getFirst() {

final Node<E> f = first;

if (f == null)

throw new NoSuchElementException();

return f.item;

}

public E element() {

return getFirst();

}

peek()和peekFirst()在链表为空时,返回null

获取位置为size-1的尾节点数据

getLast()方法在链表为空时,会抛出异常

 public E getLast() {

final Node<E> l = last;

if (l == null)

throw new NoSuchElementException();

return l.item;

}

peekLast()方法在链表为空时,返回null

  public E peekLast() {

final Node<E> l = last;

return (l == null) ? null : l.item;

}

(4)根据对象获得索引(一旦匹配,立即返回索引)

indexOf(Object o)

 //返回第一个匹配的索引

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;

}

lastIndexOf(Object o)

 //返回最后一个匹配的索引

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;

}

(5)检查链表是否包含对象

contains(Object o)

 public boolean contains(Object o) {

return indexOf(o) != -1;

}

说明:contains(Object o)方法检查对象o是否存在于链表中,此方法调用了IndexOf方法,只要返回结果不是-1,那就说明该对象存在于链表中

(6)删除指定对象

remove(Object o)

 public boolean remove(Object o) {

//如果删除对象为null

if (o == null) {

//从前向后遍历

for (Node<E> x = first; x != null; x = x.next) {

//一旦匹配,调用unlink()方法和返回true

if (x.item == null) {

unlink(x);

return true;

}

}

} else {

//从前向后遍历

for (Node<E> x = first; x != null; x = x.next) {

//一旦匹配,调用unlink()方法和返回true

if (o.equals(x.item)) {

unlink(x);

return true;

}

}

}

return false;

}

说明:当删除指定对象时,只需调用remove(Object o)即可,不过该方法一次只会删除一个匹配的对象,如果删除了匹配对象,返回true,否则false。

(7)按照位置删除对象

删除任意指定位置的对象

 public E remove(int index) {

//检查index范围

checkElementIndex(index);

//将节点删除

return unlink(node(index));

}

删除头节点的对象

remove()、removeFirst()、pop()在链表为空时将抛出NoSuchElementException

 public E remove() {

return removeFirst();

}

public E pop() {

return removeFirst();

}

public E removeFirst() {

final Node<E> f = first;

if (f == null)

throw new NoSuchElementException();

return unlinkFirst(f);

}

poll()、pollFirst()在链表为空时返回null

 public E poll() {

final Node<E> f = first;

return (f == null) ? null : unlinkFirst(f);

}

public E pollFirst() {

final Node<E> f = first;

return (f == null) ? null : unlinkFirst(f);

}

(8)删除尾节点的元素

removeLast()在链表为空将抛出NoSuchElementException

 public E removeLast() {

final Node<E> l = last;

if (l == null)

throw new NoSuchElementException();

return unlinkLast(l);

}

pollLast()方法在链表为空的时候,返回null

 public E pollLast() {

final Node<E> l = last;

return (l == null) ? null : unlinkLast(l);

}

(9)迭代器操作

iterator()、listIterator()、listIterator(int index)

 public Iterator<E> iterator() {

return listIterator();

}

public ListIterator<E> listIterator() {

return listIterator(0);

}

public ListIterator<E> listIterator(int index) {

checkPositionIndex(index);

return new ListItr(index);

}

4、内部类

(1)Node< E >

 private static class Node<E> {

E item;

Node<E> next;

Node<E> prev;

Node(Node<E> prev, E element, Node<E> next) {

this.item = element;

this.next = next;

this.prev = prev;

}

}

说明:从Node的定义可以看出链表是一个双端链表的结构

(2)ListItr

   private class ListItr implements ListIterator<E> {

private Node<E> lastReturned;

private Node<E> next;

private int nextIndex;

private int expectedModCount = modCount;//保存当前modCount,确保fail-fast机制

ListItr(int index) {

// assert isPositionIndex(index);

next = (index == size) ? null : node(index);//得到当前索引指向的next节点

nextIndex = index;

}

public boolean hasNext() {

return nextIndex < size;

}

//获取下一个节点

public E next() {

checkForComodification();

if (!hasNext())

throw new NoSuchElementException();

lastReturned = next;

next = next.next;

nextIndex++;

return lastReturned.item;

}

public boolean hasPrevious() {

return nextIndex > 0;

}

//获取前一个节点,将next节点向前移

public E previous() {

checkForComodification();

if (!hasPrevious())

throw new NoSuchElementException();

lastReturned = next = (next == null) ? last : next.prev;

nextIndex--;

return lastReturned.item;

}

public int nextIndex() {

return nextIndex;

}

public int previousIndex() {

return nextIndex - 1;

}

public void remove() {

checkForComodification();

if (lastReturned == null)

throw new IllegalStateException();

Node<E> lastNext = lastReturned.next;

unlink(lastReturned);

if (next == lastReturned)

next = lastNext;

else

nextIndex--;

lastReturned = null;

expectedModCount++;

}

public void set(E e) {

if (lastReturned == null)

throw new IllegalStateException();

checkForComodification();

lastReturned.item = e;

}

public void add(E e) {

checkForComodification();

lastReturned = null;

if (next == null)

linkLast(e);

else

linkBefore(e, next);

nextIndex++;

expectedModCount++;

}

public void forEachRemaining(Consumer<? super E> action) {

Objects.requireNonNull(action);

while (modCount == expectedModCount && nextIndex < size) {

action.accept(next.item);

lastReturned = next;

next = next.next;

nextIndex++;

}

checkForComodification();

}

final void checkForComodification() {

if (modCount != expectedModCount)

throw new ConcurrentModificationException();

}

}

三、Deque接口说明

Deque是Queue的子接口,我们知道Queue是一种队列形式,而Deque则是双向队列,它支持从两个端点方向检索和插入、删除元素

方法

当Deque当做队列使用时(先进先出FIFO),添加元素是添加到队尾,删除时删除的是头部元素,其中的方法有:

队列方法Deque方法
add(e)addLast(e)
offer(e)offerLast(e)
remove()removeFirst()
poll()pollFirst()
element()getFirst()
peek()peekFirst()

Deque也能当栈用(后进先出)。这时入栈、出栈元素都是在双端队列的头部进行,其中的方法有:

栈方法Deque方法
push(e)addFirst(e)
oop()removeFirst()
peek()peekFirst()

方法详解:

void addFirst(E e);

将对象e插入到双端队列头部,容间不足时,抛出IllegalStateException异常;

void addLast(E e);

将对象e插入到双端队列尾部,容间不足时,抛出IllegalStateException异常;

boolean offerFirst(E e);

将对象e插入到双端队列头部

boolean offerLast(E e);

将对象e插入到双端队列尾部;

E removeFirst();

获取并移除队列第一个元素,队列为空,抛出NoSuchElementException异常;

E removeLast();

获取并移除队列最后一个元素,队列为空,抛出NoSuchElementException异常;

E pollFirst();

获取并移除队列第一个元素,队列为空,返回null;

E pollLast();

获取并移除队列最后一个元素,队列为空,返回null;

E getFirst();

获取队列第一个元素,但不移除,队列为空,抛出NoSuchElementException异常;

E getLast();

获取队列最后一个元素,但不移除,队列为空,抛出NoSuchElementException异常;

E peekFirst();

获取队列第一个元素,队列为空,返回null;

E peekLast();

获取队列最后一个元素,队列为空,返回null;

boolean removeFirstOccurrence(Object o);

移除第一个满足 (onull ? enull : o.equals(e)) 的元素

boolean removeLastOccurrence(Object o);

移除最后一个满足 (onull ? enull : o.equals(e)) 的元素

void push(E e);

将对象e插入到双端队列头部;

E pop();

移除并返回双端队列的第一个元素

Iterator descendingIterator();

双端队列尾部到头部的一个迭代器;

实现场景

Deque的实现类主要分为两种场景

一般场景:

  • LinkedList大小可变的链表双端队列,允许元素为null
  • ArrayDeque大小可变的数组双端队列,不允许null

并发场景

  • LinkedBlockingDeque 如果队列为空时,获取操作将会阻塞,知道有元素添加

以上是 java集合类之LinkedList详解 的全部内容, 来源链接: utcz.com/z/391333.html

回到顶部