Linkedlist与ArrayList学习

       通过上一章的学习,可以知道LinkedList实现了Deque、List等接口,链表的实现主要依靠Entry对象。上一章已经对LinkedList的队列接口方法的进行了分析与了解,本章主要分析LinkedList中List接口方法的实现;还有ArrayList的List接口方法的学习;并且分析两种方法使用的情况。

      以下为测试代码

  

public class LinkList_ArrayList {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		getAddTime(100000);
		getAddIndexTime(100000,70000);
	}
	public static void getAddTime(int count){
		ArrayList<String> arr = new ArrayList<String>();//默认长度为10
		LinkedList<String> linl = new LinkedList<String>();
		Long arr_start = new Date().getTime();
		for(int i=1;i<=count;i++){
			arr.add(i+"");//当数组空间已满,增加原有长度的1/2
			
		}
		Long arr_end = new Date().getTime();
		System.out.println("ArrayList 增加"+count+"耗时:"+(arr_end-arr_start));
		Long linl_start = new Date().getTime();
		for(int i=1;i<=count;i++){
			linl.add(i+"");//当数组空间已满,增加原有长度的1/2
			
		}
		Long linl_end = new Date().getTime();
		System.out.println("LinkedList 增加"+count+"耗时:"+(linl_end-linl_start));
	}
	public static void getAddIndexTime(int count,int index){
		ArrayList<String> arr = new ArrayList<String>();//默认长度为10
		LinkedList<String> linl = new LinkedList<String>();
		for(int i=1;i<=count;i++){
			arr.add(i+"");//当数组空间已满,增加原有长度的1/2
		}
		Long arr_start = new Date().getTime();
		for(int i=1;i<=count;i++){
			arr.add(index,i+"");//当数组空间已满,增加原有长度的1/2
		}
		Long arr_end = new Date().getTime();
		System.out.println("ArrayList  在指定位置"+index+"增加"+count+"耗时:"+(arr_end-arr_start));
		for(int i=1;i<=count;i++){
			linl.add(i+"");//当数组空间已满,增加原有长度的1/2
			
		}
		Long linl_start = new Date().getTime();
		for(int i=1;i<=count;i++){
			linl.add(index,i+"");//当数组空间已满,增加原有长度的1/2
		}
		Long linl_end = new Date().getTime();
		System.out.println("LinkedList 在指定位置"+index+"增加"+count+"耗时:"+(linl_end-linl_start));
	}

}
      运行结果为  

      ArrayList 增加100000耗时:78
      LinkedList 增加100000耗时:94

      ArrayList  在指定位置70000增加100000耗时:7738
      LinkedList 在指定位置70000增加100000耗时:192660

     为什么同一类调用不同的插入节点方法耗时差距如此之大,为什么LinkedList 在利用索引插入时为什么效率远低于ArrayList (即使在1000W个节点也一样)?我们来看一下ArrayList与LinkedList的 add(E e)方法源码

// ArrayList的源码   
public boolean add(E e) {
	ensureCapacity(size + 1);  // Increments modCount!!此方法用于增加数组大小,如果超出数组大小之后会自动增加1/2+1的长度
	elementData[size++] = e;
	return true;
    }
//LinkedList的源码
public boolean add(E e) {
<span style="white-space:pre">	</span>addBefore(e, header);
        return true;
  }
private Entry<E> addBefore(E e, Entry<E> entry) {
<span style="white-space:pre">	</span>Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
<span style="white-space:pre">	</span>newEntry.previous.next = newEntry;
<span style="white-space:pre">	</span>newEntry.next.previous = newEntry;
<span style="white-space:pre">	</span>size++;
<span style="white-space:pre">	</span>modCount++;
<span style="white-space:pre">	</span>return newEntry;
   }

        通过分析源码可以发现,对于这两个方法增加节点的方法时间复杂度都是O(1),所以他们插入相同节点个数的时间相差无几,接下来我们可以在看一下带有索引插入节点的方法

// ArrayList的源码  
    public void add(int index, E element) {
<span style="white-space:pre">	</span>if (index > size || index < 0)
<span style="white-space:pre">	</span>    throw new IndexOutOfBoundsException(
<span style="white-space:pre">		</span>"Index: "+index+", Size: "+size);


<span style="white-space:pre">	</span>ensureCapacity(size+1);  // Increments modCount!!
<span style="white-space:pre">	</span>System.arraycopy(elementData, index, elementData, index + 1,
<span style="white-space:pre">			</span> size - index);
<span style="white-space:pre">	</span>elementData[index] = element;
<span style="white-space:pre">	</span>size++;
    }
<pre name="code" class="java">//LinkedList的源码
    public void add(int index, E element) {
        addBefore(element, (index==size ? header : entry(index)));
    }

 
 private Entry<E> entry(int index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Entry<E> e = header;
        if (index < (size >> 1)) {
            for (int i = 0; i <= index; i++)
                e = e.next;
        } else {
            for (int i = size; i > index; i--)
                e = e.previous;
        }
        return e;
    }
    private Entry<E> addBefore(E e, Entry<E> entry) {
<span style="white-space:pre">	</span>Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
<span style="white-space:pre">	</span>newEntry.previous.next = newEntry;
<span style="white-space:pre">	</span>newEntry.next.previous = newEntry;
<span style="white-space:pre">	</span>size++;
<span style="white-space:pre">	</span>modCount++;
<span style="white-space:pre">	</span>return newEntry;
    }
          通过观察源码可以发现对于ArrayList,在使用索引插入节点元素的时候,其方法的时间复杂度为O(1),影响其效率的主要原因是数组的复制过程,而 LinkedList在使用索引插入元素的时候,其时间复杂度为O(1),但是当他调用entry(index)方法会有一个遍历链表的过程,这个方法的时间复杂度为O((n+2)/4)要远大于O(1),所以在使用索引插入元素的时候,LinkedList效率远低于ArrayList。

          在List接口中有如下几个常用方法:

 isEmpty()

         contains(Object o)

         remove(Object o)

         containsAll(Collection<?> c)

         addAll(int index, Collection<? extends E> c)

         removeAll(Collection<?> c)

        retainAll(Collection<?> c)

        get(int index)

        set(int index, E element)

        add(int index, E element)

        remove(int index)

        indexOf(Object o)

        通过查看两个类的源码可以发现contains(Object o), containsAll(Collection<?> c)其中在containsAll中调用了contains,而contains有调用了indexOf(Object o)方法,所以indexOf(Object o)的执行效率影响着这三个方法,我们来看下面的代码

      

public static void getIndexOfTime(int count,int count_con,int index){
		ArrayList<String> arr = new ArrayList<String>();//默认长度为10
		LinkedList<String> linl = new LinkedList<String>();
		ArrayList<String> con = new ArrayList<String>();
		for(int i=1;i<=count;i++){
			arr.add(i+"");//当数组空间已满,增加原有长度的1/2
			
		}
		for(int i=1;i<=count;i++){
			linl.add(i+"");//当数组空间已满,增加原有长度的1/2
			
		}
		for(int i=1;i<=count_con;i++){
			int p = 400000;
			con.add((p+i)+"");//当数组空间已满,增加原有长度的1/2
		}
		Long start_indexof = new Date().getTime();
		arr.indexOf(index+"");
		Long end_indexof = new Date().getTime();
		System.out.println("ArrayList方法indexOf 在"+count+"条数据中,第"+index+"个位置需要耗时"+(end_indexof-start_indexof));
		Long startlinl_indexof = new Date().getTime();
		linl.indexOf(index+"");
		Long endlinl_indexof = new Date().getTime();
		System.out.println("LinkedList方法indexOf 在"+count+"条数据中,第"+index+"个位置需要耗时"+(endlinl_indexof-startlinl_indexof));
		Long start_contains = new Date().getTime();
		arr.contains(index+"");
		Long end_contains = new Date().getTime();
		System.out.println("ArrayList方法contains 在"+count+"条数据中,第"+index+"个位置需要耗时"+(end_contains-start_contains));
		Long startlinl_contains = new Date().getTime();
		linl.contains(index+"");
		Long endlinl_contains = new Date().getTime();
		System.out.println("LinkedList方法contains 在"+count+"条数据中,第"+index+"个位置需要耗时"+(endlinl_contains-startlinl_contains));
		Long start_containsAll = new Date().getTime();
		arr.containsAll(con);
		Long end_containsAll = new Date().getTime();
		System.out.println("ArrayList方法containsAll 在"+count+"条数据中,第"+index+"个位置需要耗时"+(end_containsAll-start_containsAll));
		Long startlinl_containsAll = new Date().getTime();
		linl.containsAll(con);
		Long endlinl_containsAll = new Date().getTime();
		System.out.println("LinkedList方法containsAll 在"+count+"条数据中,第"+index+"个位置需要耗时"+(endlinl_containsAll-startlinl_containsAll));
			
	}
<pre name="code" class="java">        getIndexOfTime(500000,5,500000)运行结果为

 

                ArrayList方法indexOf 在500000条数据中,第500000个位置需要耗时3 

                LinkedList方法indexOf 在500000条数据中,第500000个位置需要耗时9 

                ArrayList方法contains 在500000条数据中,第500000个位置需要耗时4 

                LinkedList方法contains 在500000条数据中,第500000个位置需要耗时7 

               ArrayList方法containsAll 在500000条数据中,第500000个位置需要耗时17 

               LinkedList方法containsAll 在500000条数据中,第500000个位置需要耗时26

         和我们预测的一样两个检测包含的方法的效率依赖于indexOf,而ArrayList底层时数组实现所以起indexOf效率要高于LinkedList。接下来我们分析List中三个删除方法在两个链表中的效率。下面为测试代码

	public static void getRemoveTime(int count,int element,int index){
		ArrayList<String> arr = new ArrayList<String>();//默认长度为10
		LinkedList<String> linl = new LinkedList<String>();
		ArrayList<String> con = new ArrayList<String>();
		for(int i=1;i<=count;i++){
			arr.add(i+"");//当数组空间已满,增加原有长度的1/2
			
		}
		for(int i=1;i<=count;i++){
			linl.add(i+"");
			
		}
		for(int i=1;i<=10;i++){
			int p = 250000;
			con.add((p+i)+"");//当数组空间已满,增加原有长度的1/2
		}
	
		Long start_remove = new Date().getTime();
		arr.remove(index);
		Long end_remove = new Date().getTime();
		System.out.println("ArrayList方法remove 在"+count+"条数据中,第"+index+"个位置需删除元素要耗时"+(end_remove-start_remove));
		Long startlinl_remove = new Date().getTime();
		linl.remove(index);
		Long endlinl_remove = new Date().getTime();
		System.out.println("LinkedList方法remove 在"+count+"条数据中,第"+index+"个位置需删除元素要耗时"+(endlinl_remove-startlinl_remove));
		Long start_remove_OBJ = new Date().getTime();
		arr.remove(element+"");
		Long end_remove_OBJ = new Date().getTime();
		System.out.println("ArrayList方法remove  OBJ 在"+count+"条数据中,删除"+element+"元素要耗时"+(end_remove_OBJ-start_remove_OBJ));
		Long startlinl_remove_OBJ = new Date().getTime();
		linl.remove(element+"");
		Long endlinl_remove_OBJ = new Date().getTime();
		System.out.println("LinkedList方法remove OBJ 在"+count+"条数据中,删除"+element+"位置需插入元素要耗时"+(endlinl_remove_OBJ-startlinl_remove_OBJ));
		Long start_removeAll = new Date().getTime();
		arr.removeAll(con);
		Long end_removeAll = new Date().getTime();
		System.out.println("ArrayList方法removeAll 在"+count+"条数据中,删除容器元素要耗时"+(end_removeAll-start_removeAll));
		Long startlinl_removeAll = new Date().getTime();
		linl.removeAll(con);
		Long endlinl_removeAll = new Date().getTime();
		System.out.println("LinkedList方法removeAll 在"+count+"条数据中,删除容器元素要耗时"+(endlinl_removeAll-startlinl_removeAll));
		
	}

         getRemoveTime(500000,240000,250000),运行结果(最后一个参数选择250000而不是0或者490000)

ArrayList方法remove 在500000条数据中,第250000个位置需删除元素要耗时0
LinkedList方法remove 在500000条数据中,第250000个位置需删除元素要耗时8
ArrayList方法remove  OBJ 在500000条数据中,删除280000元素要耗时3
LinkedList方法remove OBJ 在500000条数据中,删除280000位置需插入元素要耗时5
ArrayList方法remove
All 在500000条数据中,删除容器元素要耗时53
LinkedList方法remove
All 在500000条数据中,删除容器元素要耗时47

接下来将getRemoveTime方法做一下调整

public static void getRemoveTime(int count,int element,int index){
		ArrayList<String> arr = new ArrayList<String>();//默认长度为10
		LinkedList<String> linl = new LinkedList<String>();
		ArrayList<String> con = new ArrayList<String>();
		ArrayList<String> con_linl = new ArrayList<String>();
		for(int i=1;i<=count;i++){
			arr.add(i+"Hellow Word ArrayList!");//当数组空间已满,增加原有长度的1/2
			
		}
		for(int i=1;i<=count;i++){
			linl.add(i+"Hellow Word LinkedList!");
			
		}
		for(int i=1;i<=1;i++){
			int p = 2000000;
			con.add((p+i)+"Hellow Word ArrayList!");//当数组空间已满,增加原有长度的1/2
		}
		for(int i=1;i<=1;i++){
			int p = 2000000;
			con_linl.add((p+i)+"Hellow Word LinkedList!");//当数组空间已满,增加原有长度的1/2
		}
		Long start_remove = new Date().getTime();
		arr.remove(index);
		Long end_remove = new Date().getTime();
		System.out.println("ArrayList方法remove 在"+count+"条数据中,第"+index+"个位置需删除元素要耗时"+(end_remove-start_remove));
		Long startlinl_remove = new Date().getTime();
		linl.remove(index);
		Long endlinl_remove = new Date().getTime();
		System.out.println("LinkedList方法remove 在"+count+"条数据中,第"+index+"个位置需删除元素要耗时"+(endlinl_remove-startlinl_remove));
		Long start_remove_OBJ = new Date().getTime();
		arr.remove(element+"Hellow Word ArrayList!");
		Long end_remove_OBJ = new Date().getTime();
		System.out.println("ArrayList方法remove  OBJ 在"+count+"条数据中,删除"+element+"元素要耗时"+(end_remove_OBJ-start_remove_OBJ));
		Long startlinl_remove_OBJ = new Date().getTime();
		linl.remove(element+"Hellow Word LinkedList!");
		Long endlinl_remove_OBJ = new Date().getTime();
		System.out.println("LinkedList方法remove OBJ 在"+count+"条数据中,删除"+element+"元素要耗时"+(endlinl_remove_OBJ-startlinl_remove_OBJ));
		Long start_removeAll = new Date().getTime();
		arr.removeAll(con);
		Long end_removeAll = new Date().getTime();
		System.out.println("ArrayList方法removeAll 在"+count+"条数据中,删除容器元素要耗时"+(end_removeAll-start_removeAll));
		Long startlinl_removeAll = new Date().getTime();
		linl.removeAll(con_linl);
		Long endlinl_removeAll = new Date().getTime();
		System.out.println("LinkedList方法removeAll 在"+count+"条数据中,删除容器元素要耗时"+(endlinl_removeAll-startlinl_removeAll));
		
	}

运行getRemoveTime(3000000,1,1000)得到如下运行结果

ArrayList方法remove 在3000000条数据中,第1000个位置需删除元素要耗时2
LinkedList方法remove 在3000000条数据中,第1000个位置需删除元素要耗时0
ArrayList方法remove  OBJ 在3000000条数据中,删除1元素要耗时2
LinkedList方法remove OBJ 在3000000条数据中,删除1元素要耗时0
ArrayList方法removeAll 在3000000条数据中,删除容器元素要耗时119
LinkedList方法removeAll 在3000000条数据中,删除容器元素要耗时97

修改运行参数getRemoveTime(3000000,200000,2000000);得到一下运行结果

ArrayList方法remove 在3000000条数据中,第2000000个位置需删除元素要耗时0
LinkedList方法remove 在3000000条数据中,第2000000个位置需删除元素要耗时42
ArrayList方法remove  OBJ 在3000000条数据中,删除2000000元素要耗时15
LinkedList方法remove OBJ 在3000000条数据中,删除2000000元素要耗时61
ArrayList方法removeAll 在3000000条数据中,删除容器元素要耗时116
LinkedList方法removeAll 在3000000条数据中,删除容器元素要耗时92

        比较后两次的运行结果可以看出,当被移除元素越靠近链表中央LinkedList的效率越低(由于起遍历特点导致的);而ArrayList被移除元素越靠后效率越高(元素越靠后移动元素的个数越少,操作时间也越短)。通过着三个例子也可以看出,ArrayList与LinkedList的效率涉及到链表的遍历耗时与删除节点的操作时间,尤其是后者对于不同的服务器计算能力不同可能会有差异,所以需要根据实际情况来判断使用那种链表(但一般来数如果链表长度越大,ArrayList的删除效率会远低于LinedList,因为数组的移位操作比较耗时)

      LinkedList 元素越靠近链表中央,效率越低由于起遍历特点决定的,能够从下面的源码看出来

    private Entry<E> entry(int index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Entry<E> e = header;
        if (index < (size >> 1)) {
            for (int i = 0; i <= index; i++)
                e = e.next;
        } else {
            for (int i = size; i > index; i--)
                e = e.previous;
        }
        return e;
    }

            ArrayList元素越靠近链表尾端,效率越高由于起调用这个方法

          System.arraycopy(elementData, index+1, elementData, index,numMoved)。
          对于get(int index),set(int index, E element)两个方法由于没有涉及到元素的移动只有遍历和修改节点内容所以ArrayList的效率要高于LinkedList
         在两个类中,所有的方法都没有进行同步处理,所以这两个类都是线程非安全的,不支持多线,如果需要同步处理则需要进行外部同步。
         Vector为适量队列,他也实现了List接口,并且底层与ArrayList一样也是由动态数组实现,但他内部做了同步处理,线程安全。
         Stack继承自Vector,实现先进后出的功能,内部同样做了同步处理,线程安全。


     

               





















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值