LinkedList的源码分析,也来啦!!!

1、继承关系图

LinkedList的源码分析,也来啦!!!_第1张图片

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

(1)AbstractSequentialList:List的骨架实现,对AbstractList做了部分修改
(2)List:定义了链表的相关操作,有序(插入顺序)、可通过索引访问、允许存在重复元素、允许添加null元素
(3)Deque :即能将LinkedList当作双端队列使用
(4)Cloneable:一种标记接口,实现改接口能够实现克隆,若不实现该接口,用Object.clone会报错
(5)Serializable:一种标记接口,实现改接口,他支持序列化,能够通过序列化进行传输

2、源码分析

2.1 LinkedList属性

	// 链表节点个数
    transient int size = 0;
   // 头结点
    transient Node<E> first;
   // 尾结点
    transient Node<E> last;

2.2 内部类

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

LinkedList的底层数据结构是双向链表,该双向链表由一个个的Node构成,LinkedList中一个元素对应一个Node:

2.3 构造方法

(1)无参构造

public LinkedList() {
     
}

(2)Collection型构造方法

public LinkedList(Collection<? extends E> c) {
     
	this();	// 调用空构造器进行初始化
	addAll(c); // 
}

//添加所有的方法
public boolean addAll(Collection<? extends E> c) {
     
	return addAll(size, c);
}

会调用addAll()方法将集合中的元素添加到链表中,添加的操作后面会详细介绍。

2.4 添加元素

(1)add(E e)

public boolean add(E e) {
     
	linkLast(e);//默认添加在链表的尾部
	return true;
}

void linkLast(E e) {
     
	// 指向链表的尾部
    final Node<E> l = last;
    // 以链表的尾部为驱动点创建一个新的节点
    final Node<E> newNode = new Node<>(l, e, null);
    // 将链表的尾部指向新的节点
    last = newNode;
    // 如果链表为空,那么该节点既是头节点也是尾节点
    if (l == null)
        first = newNode;
     //链表不为空,那么将该结点作为原链表尾部的后继节点
   	else
    	l.next = newNode;
    // 元素的个数自增
	size++;
	// 修改次数自增
	modCount++;
}

(2)add(int index,E e)

public void add(int index, E element) {
     
	// 检查索引是否合法,index在0~size-1范围内
	checkPositionIndex(index);
	// 如果index==size,直接在链表的最后插入元素,相当于add(E e)方法
    if (index == size)
    	linkLast(element);
    // 在链表内部添加元素
    else
        linkBefore(element, node(index));
}

// 获取指定索引处的节点
Node<E> node(int 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;
    }
}

// 在链表内部添加元素的方法
// 新建值为e的节点并将其连接到succ之前(succ不为null) 
void linkBefore(E e, Node<E> succ) {
     
    // assert succ != null;
    // 指定节点的前置
	final Node<E> pred = succ.prev;
	//  新建节点,前置节点为succ的前置节点,则e元素就是要插入的元素,后置节点为succ
    final Node<E> newNode = new Node<>(pred, e, succ);
    // 构建双向链表,succ的前置节点为新的节点
    succ.prev = newNode;
    // 如果前驱节点为null,则把newNode赋值给first
    if (pred == null)
        first = newNode;
    // 构建双向链表
    else
        pred.next = newNode;
     // 元素的个数增加
    size++;
    // 修改次数增加
    modCount++;
}

(3)addAll(Collection c)

// 封装完之后依次添加在链表的尾部
public boolean addAll(Collection<? extends E> c) {
     
	// 调用addAll(size,c)方法,见(4)分析
	return addAll(size, c);
}

(4)addAll(int index, Collection c)

//将集合从指定位置开始插入
public boolean addAll(int index, Collection<? extends E> c) {
     
	// 检查索引是否在0-size之间
	checkPositionIndex(index);
	// 将有数据集合转化为数组,得到集合的数据
    Object[] a = c.toArray();
    int numNew = a.length;
    if (numNew == 0)
    	return false;
	// 得到插入位置的前置节点和后置节点
    Node<E> pred, succ;
    //如果插入位置为尾部,前置节点为last,后置节点为null
    if (index == size) {
     
        succ = null;
        pred = last;
    //否则,调用node()方法得到后置节点,再得到前置节点
    } else {
     
        succ = node(index);
        pred = succ.prev;
    }
	// 遍历数据将数据插入
    for (Object o : a) {
     
    	@SuppressWarnings("unchecked") E e = (E) o;
    	// 新建节点,这里newNode.next为null,因为其指向将是下一个遍历的节点
   	 	Node<E> newNode = new Node<>(pred, e, null);
   	 	// 插入链表的头部
   		if (pred == null)
    		first = newNode;
    	// 将新节点连接到pred之后
    	else
        	pred.next = newNode;
       // 更新pred
    	pred = newNode;
    }
	// succ为null说明if (index == size) 为ture
    if (succ == null) {
     
    	// 直接更新尾部的节点就好
        last = pred;
    // 否则,将插入的链表与先前链表连接起来
    } else {
     
        pred.next = succ;
        succ.prev = pred;
    }
	// 更新size
    size += numNew;
    modCount++;
    return true;
}

2.5 删除元素

(1)remove()

public E remove() {
     
	// 默认删除头部节点
	return removeFirst();
}


// 删除头部节点的方法
public E removeFirst() {
     
	 // 保证f为first且f不为null
    final Node<E> f = first;
    if (f == null)
       throw new NoSuchElementException();
    return unlinkFirst(f);
}

//删除头部节点
private E unlinkFirst(Node<E> f) {
     
	// assert f == first && f != null;
    final E element = f.item;
    // 获取后置节点
    final Node<E> next = f.next;
    // 设置为null,有利于GC
    f.item = null;
    f.next = null; // help GC
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}

(2)remove(int index)

	// 移除指定索引处的元素
	public E remove(int index) {
     
		// 检查索引
   		 checkElementIndex(index);
   		 return unlink(node(index));
	}
	// 删除节点
	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;
            x.prev = null;
        }

        if (next == null) {
     
        	// 删除的是最后一个元素
            last = prev;
        } else {
     
            next.prev = prev;
            x.next = null;
        }
		// 把item置为null,让垃圾回收器回收
        x.item = null;
        // 移除一个节点,size自减
        size--;
        modCount++;
        return element;
    }

(3)remove(Object o)

	public boolean remove(Object o) {
     
		// 删除第一个值为null的节点
        if (o == null) {
     
        	// 从前向后遍历
            for (Node<E> x = first; x != null; x = x.next) {
     
                if (x.item == null) {
     
                    unlink(x);
                    return true;
                }
            }
        } else {
     
        // 删除第一个值为o的节点
            for (Node<E> x = first; x != null; x = x.next) {
     
                if (o.equals(x.item)) {
     
                    unlink(x);
                    return true;
                }
            }
        }
        // 不匹配,返回false
        return false;
    }

2.6 修改元素值

	// 修改指定索引处的元素值
	public E set(int index, E element) {
     
		// 检查索引
        checkElementIndex(index);
        // 获取index处的节点
        Node<E> x = node(index);
        // 取出该节点的元素,供返回使用
        E oldVal = x.item;
        // 用新元素替换旧元素
        x.item = element;
        // 返回旧元素
        return oldVal;
    }

2.7 查看元素值

(1)get(int index) 获取指定位置的元素

	// 查看指定索引处的元素值
	public E get(int index) {
     
		// 检查索引是否合法
        checkElementIndex(index);
        // 获取索引处节点的值
        return node(index).item;
    }

(2)getFirst()获取头结点元素

	public E getFirst() {
     
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

(3)getLast() 获取尾结点的元素值

	public E getLast() {
     
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

(4)indexOf(Object o)根据对象得到索引

	// 返回第一个值为o的节点的索引
	public int indexOf(Object o) {
     
        int index = 0;
        if (o == null) {
     
        	// 寻找第一个值为null的节点的索引
            for (Node<E> x = first; x != null; x = x.next) {
     
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
     
        	// 寻找第一个值为o的节点的索引
            for (Node<E> x = first; x != null; x = x.next) {
     
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        // 未找到,返回-1
        return -1;
    }

3、ArrayList 和 LinkedList 的区别

相同点:
(1)都是List接口下的实现类具有List提供的方法
(2)二者均属于线性表,均满足:有序(插入顺序)、允许存在重复元素、允许元素值为null、可通过索引访问元素
(3)安全性:都是非线程安全的集合(都可能会出现并发性异常ConcurrentModificationException())

不同点:
(1)ArrayList底层数据结构是数组,物理地址是连续的;LinkedList底层数据结构是双向链表,由一个个节点构成,物理地址是不连续的。
(2)ArrayList需动态扩容,进行数组的创建和拷贝,其容量一般都会大于元素个数,因此会浪费部分空间,但每个元素无需分配额外空间指向其他元素;LinkedList的元素个数就是其容量,但是它的每个节点中都需额外空间来指向前驱节点和后继节点。
(3)ArrayList可通过索引计算出元素的地址,直接定位到元素所在的位置,因此查询、修改操作较快,但增加、删除操作较慢,因为增加、删除后需要移动后面所有的元素;LinkedList的所有操作需要遍历链表才能定位到指定的元素,其增加、删除操作较快,因为增加、删除后,后面的节点无需移动,但查询、修改操作相比ArrayList较慢,因为需要遍历。
(4)LinkedList因为实现了Deque双向队列接口,具有特有的方法:例如addFirst() 、 addLast()

你可能感兴趣的:(集合,链表,java)