LinkedList源码分析

-LinkedList定义

LinkedList是一个双端列表,官方是这样描述的:

List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。

注意,此实现不是同步的。如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须 保持外部同步。

在来看LinkedList类的定义:

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

实现了List能够提供链表服务,实现了Deque能够提供队列服务。

-成员变量

transient int size = 0;//链表中结点的个数

transient Node<E> first;//整个链表的头结点

transient Node<E> last;//整个链表的尾结点

//LinkedList的静态内部类,第一结点的类结构
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;
   }
}

-构造函数

  //无参数构函
  public LinkedList() {

  }
  //传入集合构建链表
  public LinkedList(Collection<? extends E> c) {
     this();
     addAll(c);//将集合中的元素全部添加到链表中去,后面会详细解析。
 }

-添加操作全家桶

添加操作很多都是复用了其他的函数,这里我们选了几个有代表性的来解析

    public void addFirst(E e) {
        linkFirst(e);//将元素添加到头部,下面的详细解析
    }
   //在头部添加,也就是添加的结点作为头结点
   private void linkFirst(E e) {
        final Node<E> f = first;//将原本的头结点保存
        //创建一个新的结点,他没有前驱结点,值为e,后继结点就是以前的头结点
        final Node<E> newNode = new Node<>(null, e, f);
        //将新的结点设置为头结点
        first = newNode;
        //如果头结点是null,说明整个链表之前就是空,所以将尾指针也指向新添加的结点
        if (f == null)
            last = newNode;
        else
        //否则的话,将原本头结点的前驱结点设置为现在的头结点,也就是刚刚添加的新结点。
            f.prev = newNode;
        size++;//链表的总容量增加一个
        modCount++;
    }

linkFirst(E e)主要将e设置为链表的头结点,之前的头结点就在第二位了,也就是它的后面。

//这两个方法做的事情相同,都是在链表尾添加元素
public boolean add(E e) {
    linkLast(e);
    return true;
}

public void addLast(E e) {
    linkLast(e);//下面是详解
}
 //在尾部添加结点
 void linkLast(E e) {
    //保存尾结点
    final Node<E> l = last;
    //创建新结点,他的前驱结点是之前的尾结点,值为e,后继结点为null,因为它自己将成为尾结点
    final Node<E> newNode = new Node<>(l, e, null);
    //将新的结点设置为尾结点
    last = newNode;
    //如果尾结点为null,那么说明之前整个链表就是空,那么把头指针也指向新结点
    if (l == null)
        first = newNode;
     else
        //否则的话,把之前尾结点的后继结点指向现在尾结点也就是新添加的结点。
        l.next = newNode;
    size++;
    modCount++;
 }
    //在指定位置前添加元素
    public void add(int index, E element) {
        checkPositionIndex(index);//健壮性检查,检查index是否合法。

        if (index == size)
            linkLast(element);//如果插入的位置在链表尾就直接调用在链表尾插入的方法了
        else
            //否则在指定位置插入,第一个参数是要插入的元素,第二个参数是index位置原来的结点。
            linkBefore(element, node(index));
    }

接下来,我们来看node(index)

 Node<E> node(int index) {
       //这是官方注释的不知道是干哈的
        // assert isElementIndex(index);

    //判断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;
        }
    }

我们再回过头来看linkBefore(element, node(index))方法。

  void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;//succ就是index位置上的结点
        //创建新结点,它的前驱结点是index结点的前驱结点,值为e,后继结点就是index位置上的结点
        final Node<E> newNode = new Node<>(pred, e, succ);
        //将之前index位置上的结点的前驱结点设置为新结点
        succ.prev = newNode;
        //如果index位置的前驱结点为null,说明index位置的结点是投结点,要在头结点之前插入元素,那么插入的元素就是新链表的头结点
        if (pred == null)
            first = newNode;
        else
            //否则的话,之前index位置上的(结点的前驱结点)的后继结点 指向新结点。(上一个括号是为了方便断句)
            pred.next = newNode;
        size++;//链表总容量增加
        modCount++;
    }

接下来看一下,之前构造函数的addAll函数。

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;//如果集合为空,返回false

        Node<E> pred, succ;//新添加元素的前驱和后继结点
        //如果插入的位置是在链表尾,那么将一个集合元素的后继设置为空,前驱结点就是之前链表的尾结点。
        if (index == size) {
            succ = null;
            pred = last;
        } else {
        否则,先找出index位置的结点,并将其前驱结点设置为一个集合元素的前驱结点
            succ = node(index);
            pred = succ.prev;
        }
        //循环添加结点
        for (Object o : a) {

            @SuppressWarnings("unchecked") E e = (E) o;
            //创建新结点,他的前驱结点在上面已经设置过了,值为e,后继结点等到null(后继结点等到下一次循环时设置)
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)//如果前驱结点是null,那么新插入的元素就是头结点
                first = newNode;
            else
            //否则就把它的前驱结点的后继结点指向新结点
                pred.next = newNode;
            //将新结点作为下一个要添加的结点的前驱结点进入下一次循环
            pred = newNode;
        }
    //将前面的链表和后面连接在一起
        if (succ == null) {//如果插入的位置是链表尾,就把最后一个插入的元素设置为尾结点
            last = pred;
        } else {
        否则的话,集合中最后一个元素的后继结点设置为index位置上的结点
        index位置上的结点的前驱结点设置为集合中最后一个元素的后继结点
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;//集合容量增加了插入的集合的长度个
        modCount++;
        return true;
 }

入队操作,实际调用的还是add()方法

public boolean offer(E e) {
        return add(e);
}
//当LinkedList被用作Stack时,入栈操作
public void push(E e) {
        addFirst(e);
}

//一个特殊的添加操作,实际是替换了index位置上的值
 public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);//找出index位置上的结点
        E oldVal = x.item;
        x.item = element;
        return oldVal;//替换后返回
  }

-获取操作全家桶

//返回指定位置的结点
public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
}
//获取头元素
public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
//获取第一个元素
public E element() {
    return getFirst();
 }
//出栈操作,不删除栈顶元素
public E peek() {
   final Node<E> f = first;
   return (f == null) ? null : f.item;
}
//获取尾元素
public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

-获取位置

//从头结点开始获取第一个与指定元素相同的值的位置
 public int indexOf(Object o) {
        int index = 0;
        if (o == null) {//当参数是null,找出链表中第一个值为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;//否则返回-1表示没找到
    }
//相似的结构,这个是从尾结点向前找,意义是返回链表中最后一个与指定元素相等的位置。
 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;
    }

-删除全家桶
删除操作也有很多复用其他函数的情况,这里我们也挑主要的函数来解析

//从权限上看,这个函数就是被内部调用的函数,主要用来删除头结点
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;//如果next为空表示链表中只有一个结点
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }
//同上面的函数的结构相似,这个函数是删除尾结点
 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;
    }
//删除指定结点,整体思想就是让指定结点的前驱结点直接指向其后继结点
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;//前驱结点的next指针直接指向后继结点
            x.prev = null;
        }

        if (next == null) {
            last = prev;//如果后继结点为空,说明该结点是尾结点
        } else {
            next.prev = prev;//该后继结点的prev指针指向前驱结点(因为是双向链表,所以删除一个元素要调动两根指针)
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }
    //根据指定的元素,先找出指定的结点,再调用unlink(x)删除该结点
    public boolean remove(Object o) {
        if (o == null) {//如果参数为null,就删除一个元素为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;
    }
//用户操作的删除头结点的接口
public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
}
//删除尾指针
public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
}
   //从头出队操作(由于是双向队列)
   public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
   }
//出栈操作
 public E pop() {
        return removeFirst();
    }

-转换数组操作

   //这里也是用来常规的循环遍历的方法
    public Object[] toArray() {
        Object[] result = new Object[size];
        int i = 0;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;
        return result;
    }

-迭代器

 //传统的Iterator在继承的AbstractSequentialList里已经实现了
 //LinkedList自己内部遍历器listIterator可以实现从指定位置遍历
 public ListIterator<E> listIterator(int index) {
        checkPositionIndex(index);
        return new ListItr(index);
    }
 //它是一个私有的内部类
   private class ListItr implements ListIterator<E> {
        private Node<E> lastReturned;//最终返回的结点
        private Node<E> next;//下一个结点
        private int nextIndex;//结点位置
        //exceptedModCount保存了当前的modCount,如果在迭代期间,有操作改变了链表的底层结构,那么再操作迭代器的方法时将会抛出ConcurrentModificationException。 
        private int expectedModCount = modCount;

        ListItr(int index) {
            // assert isPositionIndex(index);
            //找出指定位置的结点
            next = (index == size) ? null : node(index);
            nextIndex = index;
        }

        public boolean hasNext() {
            //判断后面是否还有结点
            return nextIndex < size;
        }

        public E next() {
            checkForComodification();
            if (!hasNext())
                throw new NoSuchElementException();
        //如果不为空,就返回当前结点
            lastReturned = next;
            //next指向下一个结点
            next = next.next;
            //位置加一,指向下一个结点
            nextIndex++;
            return lastReturned.item;
        }

        public boolean hasPrevious() {
            //由指定位置向前遍历,判断前面是否还有结点
            return nextIndex > 0;
        }

        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;
        }
       //在当前遍历到的结点之后插入指定结点,也就是在next()返回的结点之后插入指定结点
        public void add(E e) {
            checkForComodification();
            lastReturned = null;
            if (next == null)
                linkLast(e);//如果下一个结点是null,说明要插入的位置是尾结点
            else
                linkBefore(e, next);//否则在next之前插入(经过了next()调用之后,next=next.next,所以要在这时的next之前插入)
            nextIndex++;
            expectedModCount++;
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本系统的研发具有重大的意义,在安全性方面,用户使用浏览器访问网时,采用注册和密码等相关的保护措施,提高系统的可靠性,维护用户的个人信息和财产的安全。在方便性方面,促进了校园失物招领网的信息化建设,极大的方便了相关的工作人员对校园失物招领网信息进行管理。 本系统主要通过使用Java语言编码设计系统功能,MySQL数据库管理数据,AJAX技术设计简洁的、友好的网址页面,然后在IDEA开发平台,编写相关的Java代码文件,接着通过连接语言完成与数据库的搭建工作,再通过平台提供的Tomcat插件完成信息的交互,最后在浏览器打开系统网址便可使用本系统。本系统的使用角色可以被分为用户和管理员,用户具有注册、查看信息、留言信息等功能,管理员具有修改用户信息,发布寻物启事等功能。 管理员可以选择任一浏览器打开网址,输入信息无误后,以管理员的身份行使相关的管理权限。管理员可以通过选择失物招领管理,管理相关的失物招领信息记录,比如进行查看失物招领信息标题,修改失物招领信息来源等操作。管理员可以通过选择公告管理,管理相关的公告信息记录,比如进行查看公告详情,删除错误的公告信息,发布公告等操作。管理员可以通过选择公告类型管理,管理相关的公告类型信息,比如查看所有公告类型,删除无用公告类型,修改公告类型,添加公告类型等操作。寻物启事管理页面,此页面提供给管理员的功能有:新增寻物启事,修改寻物启事,删除寻物启事。物品类型管理页面,此页面提供给管理员的功能有:新增物品类型,修改物品类型,删除物品类型。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值