对LinkedList源码的粗略解读

目录

一、LinkedList内部结构

二、LinkedList类开头的定义

三、transient关键字修饰的变量

四、无参构造和有参构造方法

五、介绍几个比较常用操作的方法


一、LinkedList内部结构

首先LinkedList内部结构如下,其内部每一个元素都指向下一个元素:



        ┌───┬───┐   ┌───┬───┐   ┌───┬───┐   ┌───┬───┐
HEAD ──>│ A │ ●─┼──>│ B │ ●─┼──>│ C │ ●─┼──>│ D │   │
        └───┴───┘   └───┴───┘   └───┴───┘   └───┴───┘

   观察其内部结构可知,LinkedList添加删除元素时,无需移动元素,仅需改变部分结点指向的next值即可。

二、LinkedList类开头的定义

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

首先观察LinkedList类开头的定义可知,LinkedList继承自AbstractSequentialList泛型类,实现了List、Deque(双端)、Cloneable(克隆接口)、Serializable接口(序列化接口)。

AbstractSequentialList继承自AbstractList抽象类,其内部定义了根据索引拿到和设置对应位置值的方法,以及根据索引添加、删除一个元素的add、remove方法,还有添加多个元素的addAll方法等,感兴趣的可以去阅读相关源码,这里不多赘述。

三、transient关键字修饰的变量

         然后利用transient关键字定义了如下三个变量:

transient int size = 0;//列表大小

transient Node first;//头结点

transient Node last;//尾结点

        被transient关键字修饰的变量,在LinkedList类实现序列化是被自动忽略,序列化的目的是将该类写入数据库或文件中以便持久化保存,或者用于网络传输。而我们这里的列表大小、头结点、尾结点是随着创建不同列表的改变而而动态变化的,不需要写入磁盘长久保存,仅在调用时会用到,所以用transient修饰只作用于内存,不写入磁盘。

四、无参构造和有参构造方法

        接着定义了无参构造和有参构造方法,有参构造方法利用了Java的泛型机制,可传入任意类型一组对象,并调用addAll()方法将所传的这组对象全部添加进具体的LinkedList实例化对象中。

 public LinkedList() {//无参构造方法
    }


 public LinkedList(Collection c) {//有参构造方法
        this();
        addAll(c);
    }

        这里又不得不先提到addAll()方法了

        LinkedList类中定义了返回值为boolean类型的allALL()方法

//LinkedList中addAll()方法
public boolean addAll(Collection c) {
        return addAll(size, c);
    }

        返回值调用的值另一个addAll()方法,如下

//具体实现方法addAll()
public boolean addAll(int index, Collection c) {
        checkPositionIndex(index);//检查索引位置合不合法

        Object[] a = c.toArray();//将c转换成Object类型的数组a
        int numNew = a.length;//定义numNew为数组a的长度
        if (numNew == 0)//判断,若长度为0,说明未传入任何元素
            return false;//返回false

        Node pred, succ;//定义前驱结点pred,和当前结点succ
        if (index == size) {//若索引==列表大小
            succ = null; //succ置为空
            pred = last;//将尾结点last赋给pred,从最后开始添加
        } else {//否则
            succ = node(index);//根据索引找到当前结点
            pred = succ.prev;//当前节点的前驱指向pred,保存信息
        }

        for (Object o : a) {//遍历该数组
            @SuppressWarnings("unchecked") E e = (E) o;//下转
            Node newNode = new Node<>(pred, e, null);创建新结点传入前驱,当前元素值
            if (pred == null)//如果pred前驱为空,说明为头一个添加
                first = newNode;//该结点即为头结点
            else    //否则
                pred.next = newNode;//前一个结点的next指向新创建结点
            pred = newNode; //前一个节点变为新结点,以便接着添加
        }

        
        if (succ == null) {//如果当前结点为空
            last = pred;//前驱结点为尾结点
        } else {//否则
            pred.next = succ;//前驱结点的next指向当前结点
            succ.prev = pred;//当前结点的前驱prev指向前驱结点
        }

        size += numNew;//原本列表大小更新原本列表大小+加上所添加的元素个数
        modCount++;
        return true;//说明添加成功
    }

        其中要提的是checkPositionIndex(index)方法,该方法检查索引合法不合法会调用到下面这个isPositionIndex(index)方法:

private boolean isPositionIndex(int index) {//判断位置是否合法
        return index >= 0 && index <= size;//返回索引值是否在0-列表大小范围内
    }

五、介绍几个比较常用操作的方法:

1.remove()  删除元素方法

public boolean remove(Object o) {
        if (o == null) {//如果元素为null
            //从头结点开始遍历
            for (Node x = first; x != null; x = x.next) {
                if (x.item == null) {//找到当前结点的值为空的结点
                    unlink(x);//调用Unlink()删除结点x
                    return true;//返回true,删除成功
                }
            }
        } else {//元素不是null
            //依旧从头结点开始遍历
            for (Node x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {//如果当前结点的值等于要删除的值
                    unlink(x);调用Unlink()删除结点x
                    return true;//返回true,删除成功
                }
            }
        }
        return false;
    }

相关的 unlink() 方法(删除结点方法)

E unlink(Node x) {
        // assert x != null;
        final E element = x.item;//存放当前结点元素的值

        //根据x->next找到当前结点的下个结点next
        final Node next = x.next;

        //根据x->prev找到当前结点的上个结点prev
        final Node prev = x.prev;

        //若上个结点为空,说明当前结点为第一个结点
        if (prev == null) {
            first = next;//让下一个结点赋给头结点

        } else {//否则为中间结点

            //上个结点的后继next值指向当前结点的下个节点
            prev.next = next; 

            x.prev = null;//当前结点的前驱prev置为空
        }

        //若下个结点为空,说明当前结点为最后一个结点
        if (next == null) {
            last = prev;//将上一个结点赋给尾结点
        } else {
            //下一个节点的前驱指向当前结点的上个结点
            next.prev = prev;

            x.next = null;//当前结点的后继next置为空
        }

        x.item = null;//x的值置为空
        size--;//大小-1
        modCount++;
        return element;//返回元素值
    }

2.getLast() 方法:得到尾结点

//得到尾结点方法
public E getLast() {
        //l被final修饰,不可改变
        final Node l = last;//尾结点赋给结点l
        if (l == null)  //结点l为空
            //抛出没有该元素异常
            throw new NoSuchElementException();
        //否则,返回结点的item值
        return l.item;
    }

getFirst () :得到头结点方法与此类似。

3. add ()  添加方法: 默认添加到尾部

public boolean add(E e) {
        linkLast(e);//调用添加到尾部方法
        return true;//添加成功
    }

linkLast()实现机制类似unlink()方法。

4.set()  方法:根据索引将元素添加到指定位置

 public E set(int index, E element) {
        checkElementIndex(index);//检查索引位置的合法性
        Node x = node(index);//根据索引创建对应的结点x
        E oldVal = x.item; //将x的值赋给oldVal
        x.item = element; //将所要添加的元素值赋给当前结点的item
        return oldVal;//返回结点值
    }

   get()  方法: 根据索引拿到指定位置的元素

 public E get(int index) {
        checkElementIndex(index);//检查元素索引值的合法性
        return node(index).item;//返回根据索引找到的结点值
    }

5.isElementIndex():判断元素索引在不在列表范围内

private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

6.indexOf(): 根据元素值返回对应下标

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++;//index加加
            }
        } else {//元素值不为空
            //从头遍历
            for (Node x = first; x != null; x = x.next) {
                if (o.equals(x.item))//比较,若当前结点值等于传入值
                    return index;//返回当前下标
                index++;
            }
        }
        return -1;//否则没找到,返回-1
    }

LinkedList类中还有很多方法,期待下次和大家分享。

你可能感兴趣的:(java,数据结构)