手写链表之LinkedList源码分析

这是我参与新手入门的第3篇文章java

写在前面

在平常开发中,通常在对于List的场景,基本上都是经过ArrayList去封装数据的,而对于链表LinkedList相对来讲用的比较少。对我而言,好像ArrayList熟练度高一些,因此基本上也不多用LinkedList,也就是在面试的时候去背过八股文。node

image-20210619164724851

链表:数据分散的存储在物理空间中,经过一根线保存着它们之间的逻辑关系,这种存储结构称为链式存储结构,简称链表。面试

链表的前驱和后继

数据结构中,一组数据中的每一个个体被称为“数据元素”(简称“元素”)。 另外,对于具备“一对一”逻辑关系的数据,咱们一直在用“某一元素的左侧(前边)或右侧(后边)”这样不专业的词,其实线性表中有更准确的术语:数组

  • 某一元素的左侧相邻元素称为“直接前驱”,位于此元素左侧的全部元素都统称为“前驱元素”;
  • 某一元素的右侧相邻元素称为“直接后继”,位于此元素右侧的全部元素都统称为“后继元素”; image-20210619164743727

单链表

概念:单链表,用于存储逻辑关系为 "一对一" 的数据。与顺序表不一样,链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其物理存储位置是随机的。HashMap在1.7就是经过单链表来解决hash冲突的。 image-20210619170137546markdown

节点

在上图中没法提现出元素之间的逻辑关系,对此,链表的解决方案是,每一个数据元素在存储时都配备一个指针,用于指向本身的直接后继元素。表示一个节点以下:数据结构

//节点信息
class Node {
    E data;
    Node next;

    public Node(E element, Node next) {
        this.data = element;
        this.next = next;
    }

    public Node(E data) {
        this.data = data;
    }
}
复制代码

image-20210619170152515 所以,在链表中,每一个数据的存储有如下2部分组成app

  • 数据自己,其所在的区域叫作数据域ide

  • 指向后继元素的指针,叫指针域 image-20210619170343850oop

上图所示的结构在链表中称为节点。也就是说,链表实际存储的是一个一个的节点,真正的数据元素包含在这些节点中 image-20210619170416466源码分析

单链表---增

image-20210619170430403

/** * 头部添加节点 * * @param e */
 public void add(E e) {
     //头结点
     Node cur = new Node(e, list);
     list = cur;
     size++;
 }

 /** * 指定位置添加节点 * * @param index * @param e 0 1 2 3 4 */
 public void add(int index, E e) {
 	  //越界检查
     checkElementIndex(index);
     Node preNode = list;
     for (int i = 0; i < index - 1; i++) {
         //找到插入位置的前一个节点
         preNode = preNode.next;
     }
     Node node = new Node(e);
     node.next = preNode.next;
     preNode.next = node;
     size++;
 }
复制代码

单链表---删

image-20210619170441939

/** * 删除头部节点 */
public void remove() {
    if (list != null) {
        Node node = list;
        list = node.next;
        //GC
        node.next = null;
        size--;
    }
}

/** * 删除指定位置节点 * 1 2 3 4 5 * 0 1 2 3 4 * * @param index */
public void remove(int index) {
    checkElementIndex(index);
    Node preNode = list;
    for (int i = 0; i < index - 1; i++) {
        //找到指定位置元素的前一个节点
        preNode = preNode.next;
    }
    //指定位置的节点
    Node next = preNode.next;
    preNode.next = next.next;
    //GC
    next = null;
    size--;
}

/** * 删除最后一个节点 */
public void removeLast() {
    if (list != null) {
        //当前节点
        Node cur = list;
        //最后一个节点的前一个节点
        Node preNode = list;
        while (cur.next != null) {
            preNode = cur;
            cur = cur.next;
        }
        preNode = null;//此时cur已经为null
        size--;
    }
}
复制代码

单链表---改

image-20210619170454977

/** * 修改指定索引的元素 * * @param index * @param e */
public void set(int index, E e) {
    checkElementIndex(index);
    Node cur = list;
    for (int i = 0; i < index; i++) {
        cur = cur.next;
    }
    cur.data = e;
}
复制代码

单链表---查

image-20210619170505798

/** * 获取头部节点 */
 public E get() {
     if (list != null) {
         return list.data;
     } else {
         return null;
     }
 }

  /** * 获取指定位置的元素 * * @param index * @return */
  public E get(int index) {
      checkElementIndex(index);
      Node cur = list;
      for (int i = 0; i < index; i++) {
          cur = cur.next;
      }
      return cur.data;
  }
复制代码

完整代码

/** * 单链表 * */
public class SingleLinkedList<E> {

    int size = 0;

    /** * 指向第一个节点的指针 */
    Node list;

    /** * 头部添加节点 * * @param e */
    public void add(E e) {
        //头结点
        Node cur = new Node(e, list);
        list = cur;
        size++;
    }

    /** * 指定位置添加节点 * * @param index * @param e 0 1 2 3 4 */
    public void add(int index, E e) {
        checkElementIndex(index);
        Node preNode = list;
        for (int i = 0; i < index - 1; i++) {
            //找到插入位置的前一个节点
            preNode = preNode.next;
        }
        Node node = new Node(e);
        node.next = preNode.next;
        preNode.next = node;
        size++;
    }


    /** * 删除头部节点 */
    public void remove() {
        if (list != null) {
            Node node = list;
            list = node.next;
            //GC
            node.next = null;
            size--;
        }
    }

    /** * 删除指定位置节点 * 1 2 3 4 5 * 0 1 2 3 4 * * @param index */
    public void remove(int index) {
        checkElementIndex(index);
        Node preNode = list;
        for (int i = 0; i < index - 1; i++) {
            //找到指定位置元素的前一个节点
            preNode = preNode.next;
        }
        //指定位置的节点
        Node next = preNode.next;
        preNode.next = next.next;
        //GC
        next = null;
        size--;
    }

    /** * 删除最后一个节点 */
    public void removeLast() {
        if (list != null) {
            //当前节点
            Node cur = list;
            //最后一个节点的前一个节点
            Node preNode = list;
            while (cur.next != null) {
                preNode = cur;
                cur = cur.next;
            }
            preNode = null;//此时cur已经为null
            size--;
        }
    }

    /** * 修改指定索引的元素 * * @param index * @param e */
    public void set(int index, E e) {
        checkElementIndex(index);
        Node cur = list;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        cur.data = e;
    }

    /** * 获取头部节点 */
    public E get() {
        if (list != null) {
            return list.data;
        } else {
            return null;
        }
    }

    /** * 获取指定位置的元素 * * @param index * @return */
    public E get(int index) {
        checkElementIndex(index);
        Node cur = list;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        return cur.data;
    }


    class Node {
        E data;
        Node next;

        public Node(E element, Node next) {
            this.data = element;
            this.next = next;
        }

        public Node(E data) {
            this.data = data;
        }

        public Node() {
        }

        @Override
        public String toString() {
            return "Node{" +
                    "data=" + data +
                    ", next=" + next +
                    '}';
        }
    }

    /** * 判断参数是否为现有元素的索引 即边界 * * @param index */
    private void checkElementIndex(int index) {
        if (!(index >= 0 && index < size))
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
    }

    @Override
    public String toString() {
        Node node = list;
        for (int i = 0; i < size; i++) {
            System.out.print(node.data + " ");
            node = node.next;
        }
        return super.toString();
    }

    public static void main(String[] args) {
        SingleLinkedList<Integer> list = new SingleLinkedList<>();
        list.add(5);
        list.add(4);
        list.add(3);
        list.add(2);
        list.add(1);
        list.toString();
        System.out.println();
        list.add(2, 5);
        list.toString();
        list.removeLast();
        System.out.println();
        list.toString();
        list.set(1, 1);
        System.out.println();
        list.toString();
        System.out.println();
        System.out.println(list.get());
    }
}

复制代码

双链表LinkedList源码分析

双链表,指各节点之间的逻辑关系是双向的。

image-20210619170519102

节点

image-20210619170528495 所以,在双向链表中各节点包含如下 3 部分信息

  • 指针域:用于指向当前节点的直接前驱节点;
  • 数据域:用于存储数据元素。
  • 指针域:用于指向当前节点的直接后继节点;
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;
    }
}
复制代码

若是理解了单链表,双项链表其实也没有太多的差异,主要在于限制条件。不只仅是双向链表,还有不少分类,好比静态链表,动态链表,循环链表等等。这里能够就增删给出对应的过程,源码能够本身去研究研究。

类的继承关系

image-20210701233946703

双链表---增

image-20210619170540559

public void add(int index, E element) {
    checkPositionIndex(index);
	//若是索引和size相等直接尾部插入
    if (index == size)
    	linkLast(element);
    else
    	linkBefore(element, node(index));
}


/**
 * first 指向第一个节点的指针
 * @param e 要插入的元素
 * @param succ index位置的节点
 *  a1        a3
 * 在a3索引出插入新的元素  a2
 *  a1    a2    a3
 */
void linkBefore(E e, Node<E> succ) {
    // succ:原a3的前置节点a1
    final Node<E> pred = succ.prev;
    //pred(a1) e(a2) succ(a3)造成新的节点
    //即把e(a2)prev指向pred(a1)节点,把e(a2)next指向succ(a3)节点
    final Node<E> newNode = new Node<>(pred, e, succ);
    //把succ(a3)的prev指向新的节点newNode
    succ.prev = newNode;
    //pred为空表明newNode为首节点
    if (pred == null)
    	first = newNode;
    else
    	//a1的next节点由a3变为a2
    	pred.next = newNode;
    size++;
    modCount++;
}
复制代码

双链表---删

image-20210619170552552

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

/**
 * Unlinks non-null node x.
 */
 E unlink(Node<E> x) {
     // assert x != null;
     //获取该节点的值
     final E element = x.item;
     //获取该节点的next节点
     final Node<E> next = x.next;
     //获取该节点的prev节点
     final Node<E> prev = x.prev;
     //把该节点的前节点的next指向该节点的next节点,并清除该节点的prev指向
     if (prev == null) {
     	first = next;
     } else {
     	prev.next = next;
     	x.prev = null;
     }
     //把该节点的next节点的prev指向该节点的prev节点,并清除该节点的next指向
     if (next == null) {
     	last = prev;
     } else {
     	next.prev = prev;
     	x.next = null;
     }
     x.item = null;//gc清除
     size--;
     modCount++;
     return element;
 }
复制代码

总结

前面讲过ArrayList源码分析及扩容机制,若是你看了应该知道不论是用ArrayList仍是LinkedList主要是看场景,LinkedList增删快,由于只用调整指向便可,对于ArrayList而言却要移动整个数组,可是若是说是在尾部插入的话,使用二者均可以。而查找和修改却要ArrayList只须要知道下标便可,而对于LinkedList却要经过循环查找。

对于LinkendList,其中还有不少方法,例如addFirst,addLast,remove等,若是你学会了单链表,其实双链表也是同样的,主要在于思惟。

参考

拓展延伸

  • ArrayList和LinkedList有什么区别?
  • 何时用ArrayList,何时用LinkedList?
相关文章
相关标签/搜索