Collection集合分析之LinkedList

LinkedList

继承AbstractSequentialList

实现List,Dueue,Cloneable,Serializable

由链表实现

三个变量

transient int size = 0;
transient Node<E> first;
transient Node<E> last;

size:节点个数

first:头节点

last:尾节点

两个构造函数

	public LinkedList() {
    }
	public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

1.创建一个空的list

2.创建一个包含集合c所有元素的list集合

Node

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

可以看出这LinkedList构建的链表是一个双向链表

方法

getFirst

	public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

获取第一个节点的元素值,如果为空,则抛出异常,元素依旧存在于集合中

getLast

    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

获取最后一个节点的元素值,如果为空抛出异常,元素依旧存在于集合中

removeFrist

	public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

移除第一个节点且返回,通过unlikeFirst方法,为空抛出异常

	private E unlinkFirst(Node<E> f) {
        final E element = f.item;
        // 获取下一个节点的node对象
        final Node<E> next = f.next;
        // 将当前头节点存储的元素值和指向下一个节点的地址置为null
        f.item = null;
        f.next = null; // GC回收
        // 头节点变为了原头结点的下一个节点
        first = next;
        // 如果下一个节点为空,表示原先的头结点也是尾节点,last置为null
        if (next == null)
            last = null;
        else
            // 下一个节点不为空,那么指向头一个节点的指针置为null
            next.prev = null;
        // 长度减1
        size--;
        modCount++;
        return element;
    }

removeLast

	public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }

移除最后一个节点并且返回节点元素,为空则抛出异常

移除调用了unlinkLast方法

    private E unlinkLast(Node<E> l) {
        // 首先获取到最后一个节点的元素值
        final E element = l.item;
        // 获取尾节点的前一个node节点
        final Node<E> prev = l.prev;
        // 置为null。方便GC回收
        l.item = null;
        l.prev = null; 
        // 设置原尾节点的前一个节点为尾节点
        last = prev;
        // 如果为空,那么就是说尾节点是链表唯一的元素也是头结点,所以头结点也要置为null
        if (prev == null)
            first = null;
        else
            // 否则现尾节点的下一个指针的指向置为null
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

addFirst

	public void addFirst(E e) {
        linkFirst(e);
    }

头插法,将元素e构建的node节点作为头节点

调用了linkFirst方法实现

    private void linkFirst(E e) {
        // 首先获取当前的头结点
        final Node<E> f = first;
        // 构建node节点
        final Node<E> newNode = new Node<>(null, e, f);
        // 指定newNode为头节点
        first = newNode;
        // 如果f == null在没有添加之前,当前链表是空的,这个新添加的节点即是头节点也是尾节点
        if (f == null)
            last = newNode;
        else
            // 否则原先的头节点作为第二个节点
            f.prev = newNode;
        size++;
        modCount++;
    }

addLast

    public void addLast(E e) {
        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++;
    }

indexOf

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

获取元素o在链表的位置

通过遍历实现,和ArrayList基本一致

contains

    public boolean contains(Object o) {
        return indexOf(o) != -1;
    }

判断元素o是否存在于集合中,调用了indexOf方法来进行判断实现

add

    public boolean add(E e) {
        linkLast(e);
        return true;
    }

将元素e添加到链表中,从这里我们可以发现,他是使用了尾插法实现的,也就是说add方法添加元素是添加到链表末尾

remove

    public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

remove 移除链表的指定元素,首先我们根据o是否为null来查找元素,找到了移除,没有找到就返回false。

移除使用了unlink方法

    E unlink(Node<E> x) {
        // 首先x节点的元素
        final E element = x.item;
        // 获取x节点的前一个元素和后一个元素
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
        // 当前一个节点为null时,表示移除的节点为头节点,将frist往后移一个节点
        if (prev == null) {
            first = next;
        } else {
            // 否则将前一个节点的下一个节点指针指针后一个节点,将x指向前一个节点的指针指向后一个节点
            prev.next = next;
            x.prev = null;
        }

        // 当next为空,表示移除的节点为尾节点
        if (next == null) {
            // 将尾节点的指针指向头一个节点
            last = prev;
        } else {
            // 否则他就是个中间节点,将next指向前一个节点的指针指向x前一个节点
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

addAll

    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();
        int numNew = a.length;
        if (numNew == 0)
            return false;

        Node<E> pred, succ;
        if (index == size) {
            succ = null;
            pred = last;
        } else {
            succ = node(index);
            pred = succ.prev;
        }

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

        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }

在不指定index情况下,默认将c集合的所有元素添加到末尾,所以主要看的是第二个addAll方法,

首先他会判断这个index是否合理

    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }    
	private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

然后获取集合c的数组对象,和数组长度,判断这个集合有没有元素,

然后根据index判断从哪个地方插入,如果index== size表示插入到末尾,负责则获取index位置的node节点succ,和获取他的前一个node节点pred

接下来for循环就是插入操作,就是链表的插入操作,

最后的if判断 succ是否为null,为null的话,这种情况就是从尾节点进行插入的,这个时候我们要指定last,否则就是经典的从中间插入,就不多复述了,就是数据结构链表的操作,

clear

    public void clear() {
        // Clearing all of the links between nodes is "unnecessary", but:
        // - helps a generational GC if the discarded nodes inhabit
        //   more than one generation
        // - is sure to free memory even if there is a reachable Iterator
        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++;
    }

就是链表的清空操作

get

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

获取指定index位置的元素,首先判断index是否合理,然后通过node变量获取index位置的节点然后获取它的item元素。

set

public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}

set操作也是需要变量获取指定index位置的节点,然后重新指定item元素,然后原先的元素值。

add

public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

在指定位置index插入元素element,两种情况一种是尾节点插入,一种是在某一个节点后插入

linkLast之前出现过就不说了

linkBefore看看,通过node方法,我们可以获取index位置的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++;
    }

将元素e插入到succ节点前。

remove

public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}
public E remove() {
    return removeFirst();
}

第一个remove 操作移除指定index位置的元素,使用了unlink方法,

第二个套了个壳子,实则调用了removeFirst方法

lastIndexOf

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

与indexOf不同的是,前者是正序,这个是逆序查找

peek

    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

获取头节点的元素,不移除链表

element

    public E element() {
        return getFirst();
    }

获取头节点元素,不移除链表

poll

public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

获取头节点元素,且从链表中移除

总结

还有很多方法没有列出来说,但是剩下的很多方法都是套个壳子,都是已经实现了,因为这个LinkedList他不仅仅是一个链表操作,主要是他实现了Dueue,还是一个队列的实现,所以很多方法都重复调用了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值