从思想到架构之路(LinkedList源码解析)

1、回顾

上一篇我们讲ArrayList源码解析,关于ArrayList我们讲了构造函数、增删该查、优化、迭代器等,主要内容:MIN_CAPACITY_INCREMENT(开始容量),size(ArrayList的长度),array(开辟空间数组)

添加对象
add(E object)
add(int index, E object)
addAll(Collection collection)
addAll(int index, Collection collection)
删除对象
clear()
优化数组
ensureCapacity(int minimumCapacity)
查询
get(int index)
contains(Object object)
indexOf(Object object)
lastIndexOf(Object object)
删除
remove(int index)
remove(Object object)
removeRange(int fromIndex, int toIndex)
修改
set(int index, E object)

迭代器
iterator()
hasNext()
next()
remove()
这上面的方法是最主要的,有增删改查操作

2、概述
这篇文章让大家熟悉LinkedList,让大家能优化、灵活使用LinkedList,LinkedList底层使用链表模式进行构造,讲LinkedList之前我会教大家一个链表模式。
在介绍LinkedList之前我要教大家一个有用的例子,为下面介绍线程阻塞,出队,入队作为一个基础:
static final Semaphore semaphore = new Semaphore(0);
public static void main(String[] args) {
    new Thread(new Runnable() {

        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("123");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
    new Thread(new Runnable() {

        @Override
        public void run() {
            int count = 5;
            for (int i = 1; i <= count; i++) {
                if(i == count){
                    semaphore.release();
                }
                try {
                    System.out.println(i);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }).start();
}
大家可以试试这个例子,如果您有一个蓝牙例子,接收广播,开始广播和结束广播,开始完成后需要等待一段时间计算,然而等待的时间不确定,就需要开启一个为0的信号量,当动画执行完成之后释放信号量
semaphore.acquire();
System.out.println("123");
则可以输出123,也就是执行你们要做的效果。

从思想到架构之路(LinkedList源码解析)_第1张图片

接下来我们来介绍LinkedList,首先介绍LinkedList有什么内容:
1、平常增加删改:
addFirst(E e) addLast(E e) add(E e) addAll(Collection c) addAll(int index, Collection c) add(int index, E element)

remove(Object o) remove(int index) remove() removeFirst() removeLast() clear()

set(int index, E element)

get(int index) getFirst() getLast() indexOf(Object o) lastIndexOf(Object o) contains(Object o)

2、阻塞队列:
peek() element() poll() offer(E e) offerFirst(E e) offerLast(E e)
peekFirst() peekLast() pollFirst() pollLast() push(E e) pop()

那么接下来我们先来讲链表:
struct node {
char data;
node *next;
};

node *head;
int n = 0; // 链表长度

/**
建立链表
*/
void createList() {
node *p, *s;
char x;
int z = 1;
p = head;
cout << "\n\t建立一个链表" << endl;
cout << "说明:请逐个输入字符,结束标记#" << endl;
while (z) {
cout << "\t\t输入:";
cin >> x;
if (x != '#') {
s = new node;
n++;
s->data = x;
p->next = s;
s->next = NULL;
p = s;
} else {
p = head;
while (p->next != NULL) {
cout << "\t" << p->next->data;
p = p->next;
}
z = 0;
}
}
}

/**
插入节点元素
*/
void insList(int i, char x) {
node *s, *p;
int j;
s = new node;
s->data = x;
if (i == 0) {
cout << "\t\t插入位置非法!\n";
} else {
p = head;
j = 1;
while (p && j < i) {
j++;
p = p->next;
}
if (p != NULL) {
n++;
s->next = p->next;
p->next = s;
} else {
cout << "\t\t插入位置非法!\n";
}
}
}

void delList(char x) {
node *p, *q;
if (head == NULL) {
cout << "\t\t链表下溢!\n";
return;
}
if (head->next == NULL) {
cout << "\t\t链表已经为空!" << endl;
return;
}
q = head;
p = head->next;
while (p != NULL && p->data != x) {
q = p;
p = p->next;
}
if (p != NULL) {
q->next = p->next;
delete p;
n--;
cout << "\t\t" << x << "已经被删除!" << endl;
} else {
cout << "\t\t未找到!\n";
}
}

void showList() {
node *p = head;
cout << "\n\t\t显示链表的所有元素:";
if (head->next == NULL || p == NULL) {
cout << "\n\t\t链表为空!\n";
} else {
cout << "\n\t\t" << endl;
while (p->next != NULL) {
cout << p->next->data << "\t";
p = p->next;
}
cout << endl;
}
}
我们先从创建Node链表开始
struct node {
char data;
node *next;
};
下面我给一张图

从思想到架构之路(LinkedList源码解析)_第2张图片

实际上上面的链表添加、删除跟System.arrayCopy相似,添加就往后移一位然后往位置插入数据,删除就往前移一位然后就删除最后一个数据

学会了C的链表模式之后,接下来我们来看Java的LinkedList链表模式如何使用?
1、首先我们先来看新增元素:
如何新增一个元素呢?我们了解链表,假如要添加一个元素,那么我们应该如何做呢?看上面的图,我们需要往最后链接一个元素,那么源码是如何做的呢?
当执行添加操作,实际上是执行了linkLast方法来链接至最后一个元素
public boolean add(E e) {
    linkLast(e);
    return true;
}
final Node l = last;获取到最后一个元素,刚开始添加的时候last是null的,因为public LinkedList() {}构造函数没有去帮我们初始化元素,所以刚开始first、last都是为null,接下来我们来看linkLast函数源码:
1.拿到最后元素,首次创建last、first为null,创建新的节点newNode赋给首节点和最后节点
2.创建新节点让最后一个节点作为新节点的前一个,因为新创建的节点一定是最后一个节点,所以先获取到最后一个节点让新节点的前一个节点指向之前的最后节点,然后把新建的节点赋给最后节点,表示新节点创建成功,然后让前一个节点的next一个节点指向新节点
void linkLast(E e) {
    final Node l = last;
    final Node newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

下面是我创建的一个例子:
class LinkedArray {
transient int size = 0;
transient Node last;
transient Node first;

void add(E e) {
final Node l = last;
final Node newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
}
}

class Node {
E item;
Node next;
Node prev;

Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}

}

public static void main(String[] args) {
Node last = null;
Node node1 = new Node(last, "a", null);
last = node1;
Node node2 = new Node(last, "b", null);
last.next = node2;
last = node2;

LinkedArray linkedArray = new LinkedArray<>();
linkedArray.add("a");
linkedArray.add("b");
linkedArray.add("c");
linkedArray.add("d");
}
上面的例子你们可以调试一下,首先是手动创建节点,先创建尾节点为null,创建新节点,让新创建的节点的prev前一个节点,第二次创建了新节点之后让新节点的上一个尾节点的下一个节点指向该新节点,最后last = node2让新节点赋给最后一个节点,下面是教你一个链表是如何连接起来的,我们也可以联想:假如删除元素我们要怎么删除,遍历一个集合我们又该怎么做,那么接下来我们来讲删除和遍历

删除一个节点:
public E removeFirst() {
    final Node f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

private E unlinkFirst(Node f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node 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;
}
LinkedList是如何删除一个节点的呢?我们来回想System.arrayCopy,删除一个节点?将后面的节点往前移一个节点
final Node next = f.next,首先拿到当前节点的next节点,f.item = null,f.next = null置空后面的节点,
if (next == null)
    last = null;
else
    next.prev = null;
如果next节点为空,那么就把最后节点置空,如前一个果next不为null就把前一个元素置空,因为前一个元素已被删除

删除最后节点则相反
private E unlinkLast(Node l) {
    // assert l == last && l != null;
    final E element = l.item;
    final Node 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;
}

删除一个节点,假如你要删除一个节点你会怎么做,有三个节点,a、b、c,我们删除a,由于a没有前一个,所以 获取节点的上一个和下一个节点,判断当前节点是首节点或最后节点,如果是首节点,那么把下一个节点直接覆盖首节点,如果是尾节点,那么把前一个节点赋给最后节点,下面是我通过自己写的数组截的图:

从思想到架构之路(LinkedList源码解析)_第3张图片

由上图我们了解到a节点的前一个节点是null,还有c节点的next是null,那么我们删除的节点如果是最后或第一个元素的话就直接first = next,last = prev,明白意思吧,如果不明白我们回想System.arrayCopy,删除实际上就是把节点的下一个元素往前移动还有把上一个元素往后移动一位,你可以看上图的第一个元素和最后一个元素的prev和next元素,是不是null.如果非首节点和尾节点,可以看以下的图片:
从思想到架构之路(LinkedList源码解析)_第4张图片

E unlink(Node x) {
    // assert x != null;
    final E element = x.item;
    final Node next = x.next;
    final Node 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;
}

查询包含一个元素,跟ArrayList查询是否包含元素一样,查询到之后就直接返回
public boolean contains(Object o) {
    return indexOf(o) != -1;
}


public int indexOf(Object o) {
    int index = 0;
    if (o == null) {
        for (Node x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        for (Node x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}

假如我们要移除某个对象,也是一样要搜索是否包含某个对象,如果搜索到之后就用我上面说的unlink方法进行移除对象
public boolean remove(Object o) {
    if (o == null) {
        for (Node x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

接下来我们来添加一个集合,如果我们添加一个集合,我们会如何做呢?首先要获取到该集合的数组对象,然后遍历集合,将集合以链表模式添加至最后节点或某位置,如果我们添加至size最后节点,也就是调用addAll(Collection c)方法,创建预备节点为尾节点pred = last,然后遍历数组进行链表连接,Node newNode = new Node<>(pred, e, null),跟之前添加节点类似,所以这里就不说了
for (Object o : a) {
    E e = (E) o;
    Node newNode = new Node<>(pred, e, null);
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    pred = newNode;
}
完成添加节点之后需要做什么呢?把添加的节点连接至尾节点last = pred,最后size += numNew。
如果添加的节点是从某位置开始添加的
succ = node(index);
pred = succ.prev;
通过二叉法获取添加位置的节点
Node node(int index) {
    if (index < (size >> 1)) {
        Node x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}
假如index小于size的一半,Node x = first,如果index<0,那么返回的就是first,如果index在0到size/2-1的时候,那么从0到index位置获取Node节点,如果index大于size的一半,则相反.
succ = node(index)
pred = succ.prev
获取index下标的node节点,然后获取前一个节点,后面跟前面一样,把节点一个个连接起来
pred.next = succ;
succ.prev = pred;
最后把index位置的链表连接回去,先让创建好的节点的next再连接回去,然后让index位置的链表的前一个节点指向pred,这样就达到了位置链表连接

public boolean addAll(Collection c) {
    return addAll(size, c);
}

public boolean addAll(int index, Collection c) {
    checkPositionIndex(index);

    Object[] a = c.toArray();
    int numNew = a.length;
    if (numNew == 0)
        return false;

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

Node node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {
        Node x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

清除链表,首先将链表一个个清空,然后再置空first,last
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 x = first; x != null; ) {
        Node next = x.next;
        x.item = null;
        x.next = null;
        x.prev = null;
        x = next;
    }
    first = last = null;
    size = 0;
    modCount++;
}

获取下标的值,采用二叉法获取数据
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

修改下标对象的值,如果是你你觉得会怎么修改,我们想成后台的修改,是不是先查询是否包含该对象,存在则获取该对象进行修改,不存在就直接返回。set方法也一样.首先查询node(index),获取node下的值,然后修改x.item = element.
public E set(int index, E element) {
    checkElementIndex(index);
    Node x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}

添加一个属性,假如index等于长度,那么直接链接至尾部,反之,则:
void linkBefore(E e, Node succ) {
    // assert succ != null;
    final Node pred = succ.prev;
    final Node newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
}

获取该节点的前节点,判断如果前节点为null,则该节点为首节点,把新建的节点赋给首节点,如果前节点不为空则让前节点的next重新指向新的,从而断开之前的next.在链接之前都会succ.prev = newNode.总结论:我们要链接一个节点到我们的链表中,首先让开一个位置让succ节点的前一个指向新开辟的节点,让succ的前节点的前一个节点重新指向新开辟的节点.这样就达到添加一个属性.
public void add(int index, E element) {
    checkPositionIndex(index);

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

通过索引删除元素,调用的是unlink方法.具体看上面的unlink方法了解.
public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

这篇文章有点长,下一篇我来带大家学习线程阻塞队列的出队和入队

















你可能感兴趣的:(java,java,android)