Java List两种实现 ArrayList和LinkedList性能对比及源码详解

Java ADT

ArrayList

List接口的可增长数组的实现

类似于数组的特点,ArrayList的插入和删除代价比较高,但在get / set操作只会消耗常数时间。

LinkedList

List接口的双链表实现

实际上对于LinkedList而言在表的中段删除和插入仍然是一个开销很大的过程,在测试中,一个长度为10000的LinkedList重复 remove(li.size()/2)或者add(li.size()/2,arg)操作10000次,LinkedList中段的实际效率表现低于ArrayList,且差距比较明显。但LinkedList在表头插入和删除的效率远远大于ArrayList

另外有一点需要说明,LinkedList迭代器Iterator的remove()方法其效率是非常高的,因为Iterator调用remove的时候已经是基本知道了目标元素的位置。

get / set的开销相较于ArrayList是比较大的,这也是LinkedList在中段插入或删除效率低下的原因。

实例1:对比LinkedList和ArrayList在对0位置的插入效率

package ryo;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class ListTest {
	
	public static void main(String[] args) {
		set(new ArrayList<Integer>() ,10000);
		set(new LinkedList<Integer>() ,10000);
		
	}
	
	public static void set(List<Integer> li ,int times) {
		long before = System.currentTimeMillis();
		for(int i=0 ;i<times ;i++) {
			li.add(0 ,i);
		}
		long after = System.currentTimeMillis();
		System.out.println(li.getClass().getName()+" :"+(after-before)+"ms");
	}

}

实例2:对比LinkedList和ArrayList在get(length/2)的效率

package ryo;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class ListTest {
	
	public static void main(String[] args) {
		set(new ArrayList<Integer>() ,10000);
		set(new LinkedList<Integer>() ,10000);
		
	}
	
	public static void set(List<Integer> li ,int times) {
		for(int i=0 ;i<times ;i++) {
			li.add(0,1);
		}
		long before = System.currentTimeMillis();
		for(int i=0 ;i<times ;i++) {
			li.get(times/2);
		}
		long after = System.currentTimeMillis();
		System.out.println(li.getClass().getName()+" :"+(after-before)+"ms");
	}

}

但在get表头或者表尾的时候,LinkedList表现几乎与ArrayList一致,受益于LinkedList的双链表结构,靠近表尾的get操作将从表尾向前进行

实例3:对比LinkenList和ArrayList在remove(size/2)的效率

代码沿用实例2的代码,只将get改为了remove(li.size()/2);

ArrayList和LinkedList的源码解析

这里主要对LinkedList进行分析,ArrayList的实现其实并不算太难,很多操作都使用了System.arraycopy,受制于数组的特性,只能进行拷贝再复制回原数组的操作,就拿remove来说:

 
 
public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

这里还是主要想说明一下LinkedList实现,其中包含了双链表结构的实现,双链表被抽象为一个名为Node的内部类:

 
 
	//LinkedList的内部类
    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的原理还是拿remove来举例,remove源代码所用到的所有方法加上注释一并给出

 
 
//linkedList的remove
	public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
	//判断index的合法性
	private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
	//此方法将返回index位置的node
	Node<E> node(int index) {
		//这里的>>意为右移 等同于size/2
        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;
        }
    }
	
	//移除节点x的连接关系
	E unlink(Node<E> x) {
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
		//如果x是首位 则linkedList的firstnode变为x的nextnode
        if (prev == null) {
            first = next;
        } else {
            prev.next = next;//x的前节点next指向x的后节点
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;//x的后节点prev指向x的前节点
            x.next = null;
        }
        x.item = null;//这里赋null主要是为了GC该实例内存
        size--;
        modCount++;
        return element;
    }

结论:通过测试结果和原理分析不难得知,当大多数操作(不论增删改查)发生在表头或者表尾的时候,LinkedList的性能是优于ArrayList的,但当操作频繁发生在size/2的位置时,LinkedList的性能大幅降低,远不如ArrayList

这里是我自定义的一个简单的单链表结构,不是很完善,但基本功能是齐全的


package com.ryo.structure.linkedlist;

import com.ryo.structure.DataStructure;

/**
 * 自定义的一个单链表
 * 未对insert方法进行重写
 * 未设置一个检查index合法性的方法
 * @author shiin
 * @param <T> 存储类型
 */
public class SinglyLinkedList<T> implements DataStructure<T>{
	
	private Node<T> first;
	private Node<T> last;
	private int size;
	
	public SinglyLinkedList(){
		
	}
	
	public SinglyLinkedList(T[] arr) {
		this();
		addArray(arr);
	}
	
	private void addArray(T[] arr) {
		if(arr == null || arr.length == 0) {
			return;
		}
		else {
			size = arr.length;
			@SuppressWarnings("unchecked")
			Node<T>[] temp = new Node[arr.length];
			for(int i=0 ;i<size ;i++) {
				temp[i] = new Node<T>(arr[i] ,null);
			}
			for(int i=0 ;i<size-1 ;i++) {
				temp[i].next = temp[i+1];
			}
			first = temp[0];
			last = temp[size-1];
		}
	}

	@Override
	public void insert(T newEn) {
		return;
	}

	@Override
	public void delete(int index) {
		if(index < 0) {
			return;
		}
		else if(index == 0) {
			if(first.next != null) {
				first = first.next;
			}
		}
		else{
			beforeIndexNode(index).next = beforeIndexNode(index+2);
			
		}
		size--;
	}

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

	@Override
	public void add(T newEn) {
		Node<T> newlast = new Node<T>(newEn ,null);
		last.next = newlast;
		this.last = newlast;
		size++;
	}
	

	@Override
	public T get(int index) {
		if(index == 0) {
			return first.item;
		}
		else
			return beforeIndexNode(index).next.item;
	}
	
	/**
	 * 返回目标节点之前的一个节点
	 * 因为删除方法需要改变前一个节点的next
	 * @param index	目标索引
	 * @return	index-1位置的node
	 */
	private Node<T> beforeIndexNode(int index){
		if(index == 1) {
			return first;
		}
		else {
			Node<T> temp = first;
			for(int i=0 ;i<index-1 ;i++) {
				temp = temp.next;
			}
			return temp;
		}
	}
	
	/**
	 * 单链表的节点
	 * @author shiin
	 * @param <T>
	 */
	private static class Node<T>{
		T item;	
		Node<T> next;
		
		Node(T item ,Node<T> next){
			this.item = item;
			this.next = next;
		}
	}


}


猜你喜欢

转载自blog.csdn.net/my_dearest_/article/details/80010771