LinkedList源码

386 阅读23分钟

看一下linkedList的源码,也相当于复习了一下链表的增删改查

常量定义

  • int size = 0; 链表中元素的个数

  • Node first; 指向头结点

  • Node last; 指向尾结点

Node类

private static class Node<E> {    
    E item;    
    Node<E> next;    
    Node<E> prev;    
    Node(Node<E> prev, E element, Node<E> next) {...}
}

由node类的定义可以看出linkedList底层是一个双向链表

添加元素

头部添加

  1. 使用** f **存储当前链表的头节点(执行添加操作前的头节点)
  2. 新创建一个节点Node作为头结点,prev指向 null ,next指向之前的头节点f
  3. 更新first头节点(由于在头部添加了新的节点,first为新创建的节点)
  4. 如果f为null(由于该方法是添加元素在头部,f为null,创建node前链表是空的),说明是第一次执行添加。此时头结点和尾结点是相同的。把新创建的节点赋值last节点
  5. first节点的prev始终为null。由于新创建一个node追加在头部,需要f(执行添加操作前的头节点)的prev指向新创建的节点

尾部追加

  1. 使用 l存储当前链表的尾节点(执行添加操作前的尾节点)
  2. 新创建一个节点Node作为尾结点,prev指向  l  ,next置空。
  3. 更新last尾节点(由于在尾部添加了新的节点,last应为新创建的节点)
  4. 如果l为null(由于该方法是追加元素在尾部,l为null,说明创建node前链表是空的),说明是第一次执行添加。此时头结点和尾结点是相同的。此时把新创建的节点赋值first节点
  5. last节点的next始终为null。由于新创建一个node追加在尾部,需要l(执行添加操作前的最后一个节点)的next指向新创建的节点

在头部添加节点和尾部添加节点逻辑几乎一致

移除元素

E unlink(Node<E> x) {    
    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;        
        x.prev = null;    
    }    
    if (next == null) {        
        last = prev;    
    } else {        
        next.prev = prev;       
         x.next = null;    
    }   
     x.item = null;    
    size--;    
    modCount++;    
    return element;
}
  1. 记录被移除元素的前一个节点prev和后一个节点next
  2. 判断prev。为null,即删除的元素为头结点。只需更新first节点指向当前节点的next。不为null,prev的下一个节点指向next。切断删除节点与链表的关联
  3. 判断next。为null,即删除的元素为尾结点。只需更新last节点指向prev。不为null,next的上一个节点指向prev。切断节点元素与链表的关联
  • 头结点       first = 当前节点的next

  • 尾结点       last = 当前节点的prev

  • 中间节点   prev.next = next, next.prev = prev

查找节点

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

遍历链表,查找对应得到元素,如果找到了,返回下标,没有找到,返回-1;

我发现查找null和不为null的代码就一行不一样。为什么不合并呢?

if (x.item == null) //  if (o.equals(x.item))   

合并策略:
  • == 替换 equals   当查找的元素为null时,不论o还是x.item调用equals都会抛出NPE
  • equals 替换 ==   这就必须要明白equals和==的区别。 这个方法找的是Object对应的位置。我们查找的是值,而不是查找对地址的引用。

equals与==区别

  • equals()是对字符串的内容进行比较

  • ==是指对内存地址进行比较

验证

根据LinkedList的源码写了一个Node类,简单的add,indexof方法。使用‘==’判断是否相等

public class Node <E> {    private Node<E> first;    private Node<E> last;    private E data;    private Node<E> next;    public Node() {}    public Node(E data, Node<E> next) {        this.data = data;        this.next = next;    }    void addLast(E e) {        final Node<E> l = last;        final Node<E> newNode = new Node<>(e, null);        last = newNode;        if (l == null) {            first = newNode;        } else {            l.next = newNode;        }    }    public int indexOf(Object o) {        int index = 0;        for (Node<E> x = first; x != null; x = x.next) {            if (o == x.data) {                return index;            }            index++;        }        return -1;    }}
  • 首先测试Integer,输入12,预计返回0,结果返回0,正确

    Node node = new Node<>();node.addLast(12);node.addLast(12);System.out.println(node.indexOf(12));

  • 输入12345,预计返回0,结果-1,错误

    Node<Integer> node = new Node<>();node.addLast(12345);node.addLast(12345);System.out.println(node.indexOf(12345));
    

int常量池中初始化-128~127的范围,所以当为Integer i=127时,在自动装箱过程中是取自常量池中的数值,而当Integer i=128时,128不在常量池范围内,所以在自动装箱过程中需new 128,所以地址不一样。地址不一样,数值一样,‘’==‘’是false

结论

(方法结论)不能合并。 

()明明知道结果是不能替换,但还是花费时间做了验证。归根结底还是对知识掌握不牢固,对自己不自信,害怕出错。