深入理解java中的ArrayList和LinkedList


雜談最基本數據結構--"線性表":

  表結構是一種最基本的數據結構,最常見的實現是數組,幾乎在每個程序每一種開發語言中都提供了數組這個順序存儲的線性表結構實現.

 什么是線性表?

   由0個或多個數據元素組成的有限序列.如果沒有元素,稱為空表,如果存在多個元素,則第一個元素無前驅,最后一個元素無后繼,其他元素元素都有且只有一個前驅和后繼.

 ArrayList和LinkedList  

  ArrayList和LinkedList是順序存儲結構和鏈式存儲結構的表在java語言中的實現.

  ArrayList提供了一種可增長數組的實現,使用ArrayList,因為內部使用數組實現,所以,它的優點是,對於get和set操作調用花費常數時間.缺點是插入元素和刪除元素會付出昂貴的代價.因為這個操作會導致后面的元素都要發生變動,除非操作發生在集合的末端.

  鑒於這個缺點,如果需要對表結構的前端頻繁進行插入,刪除操作,那么數組就不是一個好的實現,為此就需要使用另一種結構,鏈表,而LinkedList就是基於一種雙鏈表的實現,使用它的優點就是,對於元素的插入,刪除操作開銷比較小,無論是在表的前端還是后端.但是缺點也顯而易見,對於get操作,它需要從表的一端一個一個的進行查找,因此對get的調用花費的代價也是很昂貴的.

  理解這兩個集合最好的方式就是自己去實現它,下面我們通過代碼來實現自己的arrayList和LinkedList.

實現ArrayList

  通過拜讀java中ArrayList的源碼可以發現,其實實現一個ArrayList並不難,借鑒源碼,我們也可以寫出一個簡易版的ArrayList集合表結構. 

  MyArrayList:

package com.wang.list; import java.util.Iterator; public class MyArrayList<T> implements Iterable<T> { //初始化集合的容量
    private static final int DEFAULT_CAPACITY=10; //當前元素的個數
    private int theSize; //使用一個數組來實現這個List
    private T [] theItems; public MyArrayList(){ clear(); } //清空集合元素,初始化時使用
    public void clear(){ theSize=0; ensureCapacity(DEFAULT_CAPACITY); } //通過傳入的int值大小決定是否需要擴充集合空間大小
    public void ensureCapacity(int newCapacity) { if(newCapacity<theSize) return; T[] old=theItems; theItems=(T[]) new Object[newCapacity]; for(int i=0;i<size();i++){ theItems[i]=old[i]; } } //返回當前集合元素的個數
    public int size() { return theSize; } //返回當前集合是否為空
    public boolean isEmpty(){ return size()==0; } public void trimToSize(){ ensureCapacity(size()); } //獲取指定索引下的元素值
    public T get(int index){ if(index<0||index>=size()) throw new ArrayIndexOutOfBoundsException("索引越界"); return theItems[index]; } //修改指定索引上的元素值
    public T set(int index,T value){ if(index<0||index>=size()) throw new ArrayIndexOutOfBoundsException("索引越界"); T old=theItems[index]; theItems[index]=value; return old; } //添加元素,直接指定元素,默認添加在數組末尾
    public boolean add(T value){ add(size(),value); return true; } //添加元素,指定添加位置和元素值
    public void add(int index, T value) { //如果數組元素個數已經達到指定size();那么擴展該數組,使數組容量加倍
        if(theItems.length==size()){ ensureCapacity(size()*2+1); } //從末尾元素向前遍歷,一直到index處,使index處之后的所有元素向后移動一位
        for(int i=theSize;i>index;i--){ theItems[i]=theItems[i-1]; } theItems[index]=value; //添加完元素,是集合元素個數加一
        theSize++; } //移除指定索引處的元素值,
    public T remove(int index){ T val=theItems[index]; for(int i=index;i<size()-1;i++){ theItems[i]=theItems[i+1]; } theSize--; return val; } //重寫迭代器的方法,使用下面的靜態內部類實現,注意static,如果沒有該關鍵字,會使內部類訪問不到外部的成員
 @Override public Iterator<T> iterator() { return new ArrayListIterator(); } private class ArrayListIterator implements java.util.Iterator<T>{ private int current=0; @Override public boolean hasNext() { return current<size(); } @Override public T next() { return theItems[current++]; } @Override public void remove() { MyArrayList.this.remove(--current); } } }

現在來寫一個測試類來看看我們自己寫的集合好不好用:

package com.wang.list; import java.util.Iterator; public class TestMyArrayList { public static void main(String[] args) { MyArrayList<String> list=new MyArrayList<>(); list.add("hello"); list.add("world"); list.add("I"); list.add("am"); list.add("a"); list.add("list"); System.out.println("List的元素個數為:"+list.size()); System.out.println("list的第二個元素個數為:"+list.get(1)); //將第5個元素"a",替換為"the".
        list.set(4, "the"); System.out.println("替換后的第五個元素為:"+list.get(4)); //使用iterator方式遍歷List
        Iterator<String> it = list.iterator(); while(it.hasNext()){ System.out.println(it.next()); } } }

打印結果:

List的元素個數為:6
list的第二個元素個數為:world
替換后的第五個元素為:the
hello
world
I
am
the
list

實現LinkedList

 LinkedList是一個雙鏈表的結構,在設計這個這個表結構時,我們需要考慮提供3個類.

    1.MyLinkedList類本身.

    2.Node類,該類作為靜態的內部類出現,包含一個節點上的數據,以及到前一個節點和后一個節點的鏈.

    3.LinkedListIterator類,也是一個私有類,實現Iterator接口,提供next().hannext().remove()方法.

MyLinkedList:

package com.wang.list; import java.util.Iterator; public class MyLinkedList<T> implements Iterable<T> { //保存元素個數
    private int theSize; //記錄修改的次數, //用處是:用於在迭代器遍歷時,用於判斷在迭代過程中是否發生了修改操作,如果發生了修改,則拋出異常
    private int modCount=0; //定義兩個節點,首節點和尾節點
    private Node<T> begin; private Node<T> end; public MyLinkedList(){ clear(); } //初始化一個空表
    public void clear(){ begin=new Node<T>(null, null, null); end=new Node<T>(null, begin, null); begin.next=end; theSize=0; modCount++; } public int size(){ return theSize; } public boolean isEmpty(){ return size()==0; } public boolean add(T value){ add(size(),value); return true; } public void add(int index,T value){ //在指定索引位置先找到那個索引處的節點類信息即先getNode(index).然后執行addBefore方法
 addBefore(getNode(index),value); } //在指定的那個節點P的前方插入值為value的一個元素
    private void  addBefore(Node<T> p,T value){ Node<T> newNode=new Node<T>(value, p.prev, p); newNode.prev.next=newNode; p.prev=newNode; theSize++; modCount++; } //通過索引值返回對應位置的節點類信息
    private Node<T> getNode(int index){ Node<T> p; if(index<0||index>size()){ throw new IndexOutOfBoundsException(); } if(index<(size()/2)){ p=begin.next; for(int i=0;i<index;i++){ p=p.next; } }else{ p=end; for(int i=size();i>index;i--){ p=p.prev; } } return p; } //返回指定索引處的元素值
    public T get(int index){ return getNode(index).data; } //將指定所引處的元素值修改為value
    public T set(int index,T value){ Node<T> p=getNode(index); T oldData=p.data; p.data=value; return oldData; } //移除指定索引處的元素值
    public T remove(int index){ return remove(getNode(index)); } private T remove(Node<T> p){ p.next.prev=p.prev; p.prev.next=p.next; theSize--; modCount++; return p.data; } @Override public Iterator<T> iterator() { return new LinkedListIteraor(); } private  class LinkedListIteraor implements Iterator<T>{ //保留第一個起始位置的節點
        private Node<T> current=begin.next; //記錄此刻集合修改的總次數,之后會拿modCount再和此值作比較,如果不相等,說明在迭代過程中, //集合發生了修改操作,則會拋出異常
        private int exceptedModCount=modCount; //判斷是否可以向后移動指針
        private boolean canMove=false; @Override public boolean hasNext() { return current!=end; } @Override public T next() { if(exceptedModCount!=modCount){ throw new java.util.ConcurrentModificationException(); } if(!hasNext()){ throw new java.util.NoSuchElementException(); } T nextValue=current.data; current=current.next; canMove=true; return nextValue; } @Override public void remove() { if(exceptedModCount!=modCount){ throw new java.util.ConcurrentModificationException(); } if(!canMove){ throw new IllegalStateException(); } MyLinkedList.this.remove(current.prev); canMove=false; exceptedModCount++; } } private static class Node<T> { //當前節點數據
        public T data; //到前一個節點的鏈
        public Node<T> prev; //到后一個節點的鏈
        public Node<T> next; public Node(T data, Node<T> prev, Node<T> next) { this.data = data; this.prev = prev; this.next = next; } } }

測試類TestMyLinkedList:

package com.wang.list; import java.util.Iterator; public class TestMyLinkedList { public static void main(String[] args) { MyLinkedList<Integer> list=new MyLinkedList<>(); list.add(1); list.add(5); list.add(7); list.add(6); list.add(4); list.add(2); list.add(3); for(int i=0;i<list.size();i++){ System.out.print(list.get(i)+" "); } System.out.println(); list.set(2, 8); list.remove(0); Iterator<Integer> it = list.iterator(); while(it.hasNext()){ System.out.print(it.next()+" "); } } }

打印結果:

1 5 7 6 4 2 3 
5 8 6 4 2 3

遍歷集合時進行修改操作為什么會拋出ConcurrentModificationException:

  以前,我碰到過這樣的問題,需要刪除某一個元素,首先要在先遍歷找到要刪除的元素,然后刪除它,結果會拋出一個ConcurrentModificationException()異常,錯誤代碼大概是這樣(以上面的測試類中LinkList為例):

for(Integer i:list){ if(i==6){ list.remove(6); } }

注意:我上面並沒有實現這個remove()方法,這個remove()是根據傳入的值進行刪除,而我只寫了一個根據傳入的索引位置來刪除元素的方法.這里假設我們使用的java提供的LinkedList.

  現在我們知道了原因,因為增強的for循環內部使用的是iterator迭代器的方式進行遍歷的,在遍歷過程中,如果進行修改操作會導致exceptedModCount的值和modCount的值不相等.因此會拋出ConcurrentModificationException的異常.

  正確的刪除元素的方式應該是使用iterator()的方式遍歷並進行刪除操作,因為迭代器中的remove()方法和集合中的remove()有一點不同就是,前者刪除后,會進行exceptedModCount++,因為不會拋出上面那個異常:

Iterator<Integer> it = list.iterator(); while(it.hasNext()){ if(it.next()==6){ it.remove(); } } 

注:實現這兩個集合的代碼參考了JAVA語言描述的<數據結構和算法>一書,我自己實現了一個比較丑陋的版本,不夠精致,不敢獻丑,看着參考書上又修改了一番,不喜勿噴>..<


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM