Java进阶之----LinkedList源码分析

一、在Eclipse中查看Java类库的源代码

首先要找到JDK安装目录,然后该目录下会有个src.zip文件,我们把它加载到Eclipse中就行了,然后在Eclipse中想查看某个方法或者某个类的源代码,直接选中按F3

\

 

\

 

\

下面那个Path就是你所安装JDK目录下的src.zip的路径

\

【引自:http://www.2cto.com/kf/201612/571310.html】

 使用: 

可以在 Java 源代码编辑器或代码片段编辑测试窗中选择类型、方法或字段的名称,然后对元素的定义打开编辑器。

在 Java 编辑器中,选择类型、方法或字段的名称。您也可以仅仅在名称中单击一次。 
执行下列其中一项操作: 
1.从菜单栏中,选择浏览 > 打开声明 
2.从编辑器的弹出菜单中,选择打开声明 
3.按 F3 键

或者

1.按下 Ctrl 键。 
2.在 Java 编辑器中,将鼠标移到类型、方法或字段的名称上,直到名称变为带有下划线为止。 
3.单击一次超链接。

如果具有多个同名的定义,则会显示一个对话框,您可以选择想要打开的一个定义。一个编辑器打开,它包含所选择的元素。

原文地址:http://ajava.org/course/tool/14485.html



二、Java进阶之----LinkedList源码分析

【转自:http://blog.csdn.net/zw0283/article/details/51132161】

今天在看LinkedList的源代码的时候,遇到了一个坑。我研究源码时,发现LinkedList是一个直线型的链表结构,但是我在baidu搜索资料的时候,关于这部分的源码解析,全部都说LinkedList是一个环形链表结构。。我纠结了好长时间,还以为我理解错了,最后还是在Google搜到了结果:因为我看的源码是1.7的而baidu出来的几乎全部都是1.6的。而且也没有对应的说明。在1.7之后,Oracle将LinkedList做了一些优化,将1.6中的环形结构优化为了直线型了链表结构。这里要提示一下朋友们,看源码的时候,一定要看版本,有的情况是属于小改动,有的地方可能有大改动,这样只会越看越迷糊。

好,言归正传。我们来分析一下Java中LinkedList的部分源码。(本文针对的是1.7的源码)


LinkedList的基本结构

之前我一直在说链表链表,那什么是链表?顾名思义,链表就和链子一样,每一环都要连接着后边的一环和前边的一环,这样,当我们需要找这根链子的某一环的时候,只要我们能找到链子的任意一环,都可以找到我们需要的那一环。我们看一个图,就能很好的理解了。

在LinkedList中,我们把链子的“环”叫做“节点”,每个节点都是同样的结构。节点与节点之间相连,构成了我们LinkedList的基本数据结构,也是LinkedList的核心。
我们再来看一下LinkedList在jdk1.6和1.7直接结构的区别
首先看1.7中的结构


再来看1.6中的结构

对比一下,知道区别在哪里了吧?在1.7中,去掉了环形结构,自然在代码中的也会有部分的改变。
理解了上边的结构,在分析的时候就会容易许多。


LinkedList的构造方法

LinkedList包含3个全局参数,
size存放当前链表有多少个节点。
first为指向链表的第一个节点的引用。
last为指向链表的最后一个节点的引用。

LinkedList构造方法有两个,一个是无参构造,一个是传入Collection对象的构造。
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // 什么都没做,是一个空实现  
  2. public LinkedList() {  
  3. }  
  4.   
  5. public LinkedList(Collection<? extends E> c) {  
  6.     this();  
  7.     addAll(c);  
  8. }  
  9.   
  10. public boolean addAll(Collection<? extends E> c) {  
  11.     return addAll(size, c);  
  12. }  
  13.   
  14. public boolean addAll(int index, Collection<? extends E> c) {  
  15.     // 检查传入的索引值是否在合理范围内  
  16.     checkPositionIndex(index);  
  17.     // 将给定的Collection对象转为Object数组  
  18.     Object[] a = c.toArray();  
  19.     int numNew = a.length;  
  20.     // 数组为空的话,直接返回false  
  21.     if (numNew == 0)  
  22.         return false;  
  23.     // 数组不为空  
  24.     Node<E> pred, succ;  
  25.     if (index == size) {  
  26.         // 构造方法调用的时候,index = size = 0,进入这个条件。  
  27.         succ = null;  
  28.         pred = last;  
  29.     } else {  
  30.         // 链表非空时调用,node方法返回给定索引位置的节点对象  
  31.         succ = node(index);  
  32.         pred = succ.prev;  
  33.     }  
  34.     // 遍历数组,将数组的对象插入到节点中  
  35.     for (Object o : a) {  
  36.         @SuppressWarnings("unchecked") E e = (E) o;  
  37.         Node<E> newNode = new Node<>(pred, e, null);  
  38.         if (pred == null)  
  39.             first = newNode;  
  40.         else  
  41.             pred.next = newNode;  
  42.         pred = newNode;  
  43.     }  
  44.   
  45.     if (succ == null) {  
  46.         last = pred; // 将当前链表最后一个节点赋值给last  
  47.     } else {  
  48.         // 链表非空时,将断开的部分连接上  
  49.         pred.next = succ;  
  50.         succ.prev = pred;  
  51.     }  
  52.     // 记录当前节点个数  
  53.     size += numNew;  
  54.     modCount++;  
  55.     return true;  
  56. }  
这里要说明一下,Node是LinkedList的内部私有类,它的组成很简单,只有一个构造方法。
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private static class Node<E> {  
  2.     E item;  
  3.     Node<E> next;  
  4.     Node<E> prev;  
  5.   
  6.     Node(Node<E> prev, E element, Node<E> next) {  
  7.         this.item = element;  
  8.         this.next = next;  
  9.         this.prev = prev;  
  10.     }  
  11. }  
构造方法的参数顺序是:前继节点的引用,数据,后继节点的引用。

有了上边的说明,我们来看LinkedList的构造方法。
这段代码还是很好理解的。我们可以配合图片来深入理解。
这段代码分为了2种情况,一个是原来的链表是空的,一个是原来的链表有值。我们分别来看
原来有值的情况




配合代码来看,是不是思路清晰了许多?
原来链表是空的话就更好办了,直接把传入的Collection对象转化为数组,数组的第一个值就作为头结点,即head,之后的顺序往里加入即可。并且节省了改变原节点指向的的操作。

对与两种构造方法,总结起来,可以概括为:无参构造为空实现。有参构造传入Collection对象,将对象转为数组,并按遍历顺序将数组首尾相连,全局变量first和last分别指向这个链表的第一个和最后一个。

LinkedList部分方法分析

addFirst/addLast分析

我们来看代码
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public void addFirst(E e) {  
  2.     linkFirst(e);  
  3. }  
  4.   
  5. private void linkFirst(E e) {  
  6.     final Node<E> f = first;  
  7.     final Node<E> newNode = new Node<>(null, e, f); // 创建新的节点,新节点的后继指向原来的头节点,即将原头节点向后移一位,新节点代替头结点的位置。  
  8.     first = newNode;  
  9.     if (f == null)  
  10.         last = newNode;  
  11.     else  
  12.         f.prev = newNode;  
  13.     size++;  
  14.     modCount++;  
  15. }  
其实只要理解了上边的数据结构,这段代码是很好理解的。
加入一个新的节点,看方法名就能知道,是在现在的链表的头部加一个节点,既然是头结点,那么头结点的前继必然为null,所以这也是Node<E> newNode = new Node<>(null, e, f);这样写的原因。
之后将first指向了当前链表的头结点,之后对之前的头节点进行了判断,若在插入元素之前头结点为null,则当前加入的元素就是第一个几点,也就是头结点,所以当前的状况就是:头结点=刚刚加入的节点=尾节点。若在插入元素之前头结点不为null,则证明之前的链表是有值的,那么我们只需要把新加入的节点的后继指向原来的头结点,而尾节点则没有发生变化。这样一来,原来的头结点就变成了第二个节点了。达到了我们的目的。

addLast方法在实现上是个addFirst是一致的,这里就不在赘述了。有兴趣的朋友可以看看源代码。
其实,LinkedList中add系列的方法都是大同小异的,都是创建新的节点,改变之前的节点的指向关系。仅此而已。

getFirst/getLast方法分析

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public E getFirst() {  
  2.     final Node<E> f = first;  
  3.     if (f == null)  
  4.         throw new NoSuchElementException();  
  5.     return f.item;  
  6. }  
  7.   
  8. public E getLast() {  
  9.     final Node<E> l = last;  
  10.     if (l == null)  
  11.         throw new NoSuchElementException();  
  12.     return l.item;  
  13. }  
这段代码即不需要解析了吧。。很简单的。

get方法分析

这里主要看一下它调用的node方法
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public E get(int index) {  
  2.     // 校验给定的索引值是否在合理范围内  
  3.     checkElementIndex(index);  
  4.     return node(index).item;  
  5. }  
  6.   
  7. Node<E> node(int index) {  
  8.     if (index < (size >> 1)) {  
  9.         Node<E> x = first;  
  10.         for (int i = 0; i < index; i++)  
  11.             x = x.next;  
  12.         return x;  
  13.     } else {  
  14.         Node<E> x = last;  
  15.         for (int i = size - 1; i > index; i--)  
  16.             x = x.prev;  
  17.         return x;  
  18.     }  
  19. }  
一开始我很费解,这是要干嘛?后来我才明白,代码要做的是:判断给定的索引值,若索引值大于整个链表长度的一半,则从后往前找,若索引值小于整个链表的长度的一般,则从前往后找。这样就可以保证,不管链表长度有多大,搜索的时候最多只搜索链表长度的一半就可以找到,大大提升了效率。

removeFirst/removeLast方法分析

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public E removeFirst() {  
  2.     final Node<E> f = first;  
  3.     if (f == null)  
  4.         throw new NoSuchElementException();  
  5.     return unlinkFirst(f);  
  6. }  
  7.   
  8. private E unlinkFirst(Node<E> f) {  
  9.     // assert f == first && f != null;  
  10.     final E element = f.item;  
  11.     final Node<E> next = f.next;  
  12.     f.item = null;  
  13.     f.next = null// help GC  
  14.     first = next;  
  15.     if (next == null)  
  16.         last = null;  
  17.     else  
  18.         next.prev = null;  
  19.     size--;  
  20.     modCount++;  
  21.     return element;  
  22. }  

摘掉头结点,将原来的第二个节点变为头结点,改变frist的指向,若之前仅剩一个节点,移除之后全部置为了null。

对于LinkedList的其他方法,大致上都是包装了以上这几个方法,没有什么其他的大的变动。

三、完整实例

【转自:https://github.com/onlyliuxin/coding2017/blob/master/group20/1107837739/1107837739Learning/src/org/korben/list/KLinkedList.java】

(1)KList.java

package org.korben.list;

/**
 * Korben's List
 *
 * Created by Korben on 18/02/2017.
 */
public interface KList<T> {

    int size();

    boolean isEmpty();

    boolean contains(Object o);

    Object[] toArray();

    boolean add(T o);

    boolean remove(T o);

    void clear();

    T get(int index);

    T set(int index, T element);

    void add(int index, T element);

    T remove(int index);

    int indexOf(T o);

    KIterator<T> iterator();
}

(2)KLinkedList.java——存在点bug;不过大体思路给出来了

package org.korben.list;

import java.util.Objects;

/**
 * Korben's LinkedList
 *
 * Created by Korben on 18/02/2017.
 */
public class KLinkedList<T> implements KList<T> {

    private int size;

    private Node<T> head;

    private Node<T> last;

    public KLinkedList() {
        this.head = new Node<>(null);
    }

    @Override
    public int size() {
        return this.size;
    }

    @Override
    public boolean isEmpty() {
        return this.size == 0;
    }

    @Override
    public boolean contains(Object o) {
        Node node = this.head;
        while (node.next != null) {
            node = node.next;
            if (Objects.equals(node.data, o)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Object[] toArray() {
        return new Object[0];
    }

    @Override
    public boolean add(T o) {
        if (this.last == null) {
            this.last = new Node<>(o);
            this.last.pre = this.head;
            this.head.next = this.last;
        } else {
            Node<T> oldLast = this.last;
            this.last = new Node<>(o);
            this.last.pre = oldLast;
            oldLast.next = this.last;
        }
        this.size++;
        return true;
    }

    @Override
    public boolean remove(T o) {
        Node node = this.head;
        while (node.next != null) {
            node = node.next;
            if (Objects.equals(node.data, o)) {
                node.pre.next = node.next;
                if (node.next != null) {
                    node.next.pre = node.pre;
                }
                this.size--;
                return true;
            }
        }
        return false;
    }

    @Override
    public void clear() {
        this.head.next = null;
        this.last = null;

        this.size = 0;
    }

    @Override
    public T get(int index) {
        return getNode(index).data;
    }

    @Override
    public T set(int index, T element) {
        Node<T> node = getNode(index);
        node.data = element;
        return element;
    }

    @Override
    public void add(int index, T element) {
        Node<T> node = this.head;
        for (int i = 0; i <= index; i++) {
            node = node.next;
        }
        Node<T> pre = node.pre;
        Node<T> newNode = new Node<>(element);
        pre.next = newNode;
        newNode.pre = pre;
        newNode.next = node;
        node.pre = newNode;

        this.size++;
    }

    @Override
    public T remove(int index) {
        Node<T> node = getNode(index);
        Node<T> pre = node.pre;
        Node<T> next = node.next;
        pre.next = next;
        if (next != null) {
            next.pre = pre;
        }

        this.size--;
        return node.data;
    }

    @Override
    public int indexOf(T o) {
        Node node = this.head;
        int index = 0;
        while (node.next != null) {
            node = node.next;
            if (Objects.equals(node.data, o)) {
                return index;
            }
            index++;
        }
        return -1;
    }

    @Override
    public KIterator<T> iterator() {
        throw new IllegalStateException("方法未实现");
    }

    private Node<T> getNode(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException();
        }

        Node<T> node = this.head;
        for (int i = 0; i <= index; i++) {
            node = node.next;
        }
        return node;
    }

    private static class Node<T> {
        T data;
        Node<T> pre;
        Node<T> next;

        Node(T data) {
            this.data = data;
        }
    }
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值