ArrayList LinkedList源碼解析

来源:http://www.cnblogs.com/lewis0077/archive/2016/03/18/5275061.html
-Advertisement-
Play Games

在java中,集合這一數據結構應用廣泛,應用最多的莫過於List介面下麵的ArrayList和LinkedList; 我們先說List, 下麵我們看一看ArrayList,ArrayList是基於數組的方式來實現數據的增加、刪除、修改、搜索的。 ArrayList內部維護者兩個變數: 我們再看一看A


在java中,集合這一數據結構應用廣泛,應用最多的莫過於List介面下麵的ArrayList和LinkedList;

我們先說List,

 1 public interface List<E> extends Collection<E> {
 2     //返回list集合中元素的數量,若數量大於Integer.MAX_VALUE,則返回Integer.MAX_VALUE
 3     int size();
 4 
 5     //判讀集合內是否沒有元素,若沒有元素返回true
 6     boolean isEmpty();
 7 
 8    //判斷集合內是否包含指定的元素o
 9     boolean contains(Object o);
10 
11     //以適當的序列,返回該集合元素中的一個迭代器
12     Iterator<E> iterator();
13 
14     //返回一個數組,該數組包含該集合中所有的元素(以從first到last的順序)
15     Object[] toArray();
16 
17    //把集合中的元素放到數組a中,並返回
18     <T> T[] toArray(T[] a);
19
20    
21     //向集合末尾中添加一個元素
22     boolean add(E e);
23 
24    //從集合中刪除第一個出現的元素o
25     boolean remove(Object o);
26 
27 
28     //判斷集合中是否包含 指定集合c中的所有元素
29     boolean containsAll(Collection<?> c);
30 
31    //將指定集合c中的所有元素都追加到 集合的末尾
32     boolean addAll(Collection<? extends E> c);
33 
34    //將指定集合c中的所有元素都插入到 集合中,插入的開始位置為index
35     boolean addAll(int index, Collection<? extends E> c);
36 
37     //將指定集合c中的所有元素從本集合中刪除
38     boolean removeAll(Collection<?> c);
39 
40     //本集合和 集合c的交集
41     boolean retainAll(Collection<?> c);
42 
43    //清除集合中的元素
44     void clear();
45 
46     //比較指定對象o和本集合是否相等,只有指定對象為list,size大小和本集合size一樣,且每個元素equal一樣的情況下,才返回true
47     boolean equals(Object o);
48 
49     
50     int hashCode();
51 
52     //返回指定位置index的元素
53     E get(int index);
54 
55    //將元素element設置到集合的index位置(替換)
56     E set(int index, E element);
57 
58    //將元素element插入到集合的index位置
59     void add(int index, E element);
60 
61     //移除指定位置index的元素
62     E remove(int index);
63 
64    //返回指定對象o在本集合中的第一個索引位置
65     int indexOf(Object o);
66 
67    //返回指定對象o在本集合中的最後一個索引位置
68     int lastIndexOf(Object o);
69 
70     //返回一個ListIterator的迭代器
71     ListIterator<E> listIterator();
72 
73     //從指定位置index開始返回一個ListInterator迭代器
74     ListIterator<E> listIterator(int index);
75 
76    //返回從位置fromIndex開始到位置toIndex結束的子集合
77     List<E> subList(int fromIndex, int toIndex);
78 }

 下麵我們看一看ArrayList,ArrayList是基於數組的方式來實現數據的增加、刪除、修改、搜索的。

ArrayList內部維護者兩個變數:



//該數組緩存者集合中的元素,集合的容量就是該數組的長度,elementData用transient修飾,說明在序列化時,數組elementData不在序列化範圍內。
private transient Object[] elementData;

//集合的大小 (集合中元素的數量)
private int size;

我們再看一看ArrayList的構造器:

/**
*構造一個指定容量initialCapacity的空的集合,
*super() 是調用AbstractList的預設構造器方法,該方法是一個空的方法, 
*然後判斷傳入的參數initialCapacity不能小於0,否則就直接拋出非法參數異常;
*最後直接創建了一個長度為initialCapacity的數組對象,並將該對象賦值給當前實例的elementData屬性,用以存放集合中的元素。
*/
 public ArrayList(int initialCapacity) {
    super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
    this.elementData = new Object[initialCapacity];
}
/**
*構造一個預設的容量為10的空的集合,我們平時最經常使用的List<T> list = new ArrayList<T>();是預設創建了容量為10的集合。
*/
public ArrayList() {
    this(10);
}

/**
*構造一個包含了指定集合c中的所有元素的集合,該集合中元素的順序是以集合c的迭代器返回元素的順序。
*/
public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); }

從上面的源碼中我們看到,先將c.toArray()方法的返回值賦值給elementData,將elementData.length賦值給size, 然後進行了一個判斷 if(elementData.getClass() != Object[].class),若為真,則調用Arrays.copyOf()方法創建一個新Object[]數組,將原來elementData中的元素copy到新建的Object[]數組中,最後將新建的數組賦值給elementData。

我們看一下Arrays.copyOf()方法的源碼:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
}

如果newType類型為Object[].class的話,則直接創建一個長度為newLength的Object數組,否則使用Array.newInstance(Class<?> componentType, int length)方法創建一個元素類型為newType.getComponentType() (該方法返回數組中元素的類型)類型的,長度為newLength的數組,這是一個native方法,然後使用System.arraycopy() 這個native方法將original數組中的元素copy到新創建的數組中,並返回該數組。

我們註意 c.toArray might (incorrectly) not return Object[],按理說一個c.toArray()返回的是一個Object[]類型,其getClass()返回的也一定是Object[].class,那為什麼還要進行逐個判斷呢? 可真實情況真的是這樣嗎?我們看下麵的示例:

 

//定義一個父類Animal
public class Aniaml  {

}

//定義Animal的子類Person
public class Person extends Aniaml{
    private int id;
    private String name;
    private Date birthday;

    public Person(int id, String name, Date birthday) {
        this.id = id;
        this.name = name;
        this.birthday = birthday;
    }

    public static void main(String[] args) {
     test1();
     test2();
        test3();
    }

    public static void test1(){
        Person[] persons = {new Person(100,"lewis",new Date()),new Person(100,"lewis",new Date())};
        //class [Lcom.lewis.guava.Person;  Person的數組類型
        System.out.println(persons.getClass());

        Aniaml[] aniamls = persons;
        //class [Lcom.lewis.guava.Person;  Person的數組類型
        System.out.println(aniamls.getClass());

        //class com.lewis.guava.Person  Person類型
        System.out.println(aniamls[0].getClass());

        //java.lang.ArrayStoreException  animals[]數組中實際存儲的是Peron類型,當運行時放入非Person類型時會報錯ArrayStoreException
        aniamls[0] = new Aniaml();
    }

    public static void test2(){
        List<String> list = Arrays.asList("abc");
        //class java.util.Arrays$ArrayList 由此可見該類型不是ArrayList類型
        System.out.println(list.getClass());

        Object[] objects = list.toArray();
        //class [Ljava.lang.String;  返回的是String數組類型
        System.out.println(objects.getClass());

        //java.lang.ArrayStoreException: java.lang.Object  當我們將一個Object放入String數組時報錯ArrayStoreException
        objects[0] = new Object();
    }

    public static void test3(){
        List<String> dataList = new ArrayList<String>();
        dataList.add("");
        dataList.add("hehe");
      //class java.util.ArrayList 
        System.out.println(dataList.getClass());

        Object[] objects1 = dataList.toArray();
      //class [Ljava.lang.Object;
        System.out.println(objects1.getClass());
        objects1[0]="adf";
        objects1[0]=123;
        objects1[0]=new Object();
    }
}

 

我們由上面test2()方法可知,一個List,調用list.toArray()返回的Object數組的真實類型不一定是Object數組([Ljava.lang.Object;),當我們調用 Object[] objArray = collection.toArray(), objArray並不一定能夠存放Object對象,所以上面的源碼中對這種情況進行了判斷。

 我們接著看ArrayList中的方法:

add(E),當我們添加數據的時候,會遇到的一個問題就是:當裡面的數組滿了,沒有可用的容量的怎麼辦?

/**
*插入對象,首先將size+1,產生一個minCapacity的變數,調用ensureCapacity(minCapacity)方法保證數組在插入一個元素時有可用的容量,然後將元素e放到數組elementData的size位置,最後將size+1
*/
public boolean add(E e) {
    ensureCapacity(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

/**
*必要時數組的擴容 (如果想調整ArrayList的容量增長策略,可以繼承ArrayList,override ensureCapacity()方法即可),
首先將modCount+1,modeCount表示修改數組結構的次數(維護在父類AbstractList中),如果入參minCapacity大於目前數組elementData的容量,則將容量擴展到 (oldCapacity * 3)/2 + 1,
若此時容量仍然小於 minCapacity,則直接將minCapacity設置為最新的容量,
最後使用Arrays.copyof()方法將原來elementData中元素copy到新的數組中,並將新數組賦值給elementData.
*/ public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3)/2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } }

 

/**
*在指定下標Index處插入元素element,首先判斷下標index的參數是否有效(不能小於0,不能大於size),否則拋出IndexOutOfBoundsException異常,然後調用ensureCapacity(size+1)要確保數組中有足夠的容量來插入數據,
*然後調用System.arraycopy()方法, 從index下標開始,將elementData數組元素copy到 從index+1開始的地方,copy的長度為size-index,這樣index下標處的位置就空了出來,然後將元素element放到下標為index處的位置,最後將size++.
*我們看到使用這個方法相比add(E),多了一步數組元素的複製的代價。
*/ public void add(int index, E element) { if (index > size || index < 0) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); ensureCapacity(size+1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }

 

/**
*將元素element設值到下標為index處,返回原來index處的舊值。
*/
public E set(int index, E element) {
    RangeCheck(index);
//獲取index下標的舊值 E oldValue
= (E) elementData[index];
//設值新的元素element到index處 elementData[index]
= element; return oldValue; } //邊界檢查,index越界的話,拋出異常IndexOutOfBoundsException private void RangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); }

 

/**
*將指定集合c中的元素按照其迭代器返回的順序追加到本集合中,只要將任何一個元素插入到集合中都會返回true
*/
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
        int numNew = a.length;
     //確保(size+a.length)有足夠的容量去插入新元素
    ensureCapacity(size + numNew);  // Increments modCount
    //將數組a的內容追加到elementData數組的最後
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
//只要插入的元素個數>0就返回true
return numNew != 0; }

 

我們再看刪除的方法

/**
*刪除指定元素o,分為兩種情況,若指定元素o為null,則遍歷當前的elementData數組,如果某一個下標index上面的值為null(==),則調用方法fastRemove(int)快速刪除該元素,然後返回true
*若指定元素o不為0,則遍歷elementData數組,若某一個下標index處的元素和指定元素o 進行equals 為true的話,則調用fastRemove(int)快速刪除該元素,然後返回true
*由下麵的源碼可知,只能刪除第一個匹配的元素。
*/ public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } /** *快速刪除指定下標index的元素,不做邊界檢查(該方法時private的) */ private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0)
       //裡面進行了數組元素的移動,將index後面的元素往前複製一位 System.arraycopy(elementData, index
+1, elementData, index, numMoved);
     //將數組elementData中最後一個位置置為null,以便釋放已用,讓gc 回收 elementData[
--size] = null; // Let gc do its work }
/**
*刪除指定下標index處的元素,該方法相比remove(Object o)方法,多了一個邊界檢查,但是少了元素的查找過程,因此性能更好一些。
*/
public E remove(int index) {
//對入參index做邊界檢查 RangeCheck(index); modCount
++;
  //取出index位置的元素 E oldValue
= (E) elementData[index]; int numMoved = size - index - 1; if (numMoved > 0)
//進行數組元素的移動 System.arraycopy(elementData, index
+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work //返回原來index位置的舊元素 return oldValue; }

 

元素的搜索:

/**
*獲取指定下標index處的元素,先進行邊界檢查,然後直接返回elementData數組中對應位置index處的元素。
*/
public E get(int index) {
    RangeCheck(index);

    return (E) elementData[index];
}

 

/**
*判斷集合中是否包含指定元素o,調用indexOf(Object o)方法實現
*/
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

/**
*返回指定元素o在數組elementData首次出現的下標,這裡也是分為兩種情況:
*1.指定元素o為null 2.指定元素o不為null,在查詢元素的過程中,o為null,使用 == 比較,o不為null,使用equals比較,若找到了該元素,則返回其在數組elementData中的下標index,若找不到這返回-1.
*/ public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; }

與indexOf(Object o)方法類似的是 lastIndexOf(Object o)方法,不同的是 後者返回的是最後一次出現指定元素o的位置下標。

 public int lastIndexOf(Object o) {
    if (o == null) {
        for (int i = size-1; i >= 0; i--)
        if (elementData[i]==null)
            return i;
    } else {
        for (int i = size-1; i >= 0; i--)
        if (o.equals(elementData[i]))
            return i;
    }
    return -1;
}

 

我們再看一下ArrayList的迭代器方法如何實現的:

/**
*該方法是有ArrayList的父類AbstractList持有的,返回的是一個Itr對象
*/
public Iterator<E> iterator() {
    return new Itr();
}

我們看看Itr是個什麼鬼:

/**
*Itr 實現了Iterator介面,是AbstractList中的一個內部類。
*/
private class Itr implements Iterator<E> {
    //當前迭代器指向的數組中元素的下一個元素的位置
    int cursor = 0;

    int lastRet = -1;

    int expectedModCount = modCount;
    //每次調用hasNext方法時,判斷當前指向的數組中的位置和數組大小是否相等,若不等則返回true(說明還有值),若相等則返回false(說明已經迭代到了數組的末尾了,沒有元素了)
    public boolean hasNext() {
            return cursor != size();
    }
    //調用next方法是,先調用checkForComodification()方法,判斷是否有其他線程對集合大小做出了有影響的動作;
//然後調用get方法獲取相應位置的元素,若獲取不到,則拋出IndexOutOfBoundsException異常,在捕獲到該異常後,調用checkForComodification()方法,
//檢測modcount若== expectedModCount(沒有其他線程對集合大小做出了有影響的操作),則拋出NoSuchElementException,若modcount != expectedModCount,則拋出ConcurrentModificationException
public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public void remove() { if (lastRet == -1) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } //若modCount和期望的expectedModCount不相等,說明在迭代過程中,有其他的線程對集合大小產生了影響的動作(新增、刪除),此時拋出異常ConcurrentModificationException final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }

我們在看一個方法trimToSize

/**
*將elementData數組的容量 縮小為該集合實際包含的元素數量
*/
public void trimToSize() {
    modCount++;
    int oldCapacity = elementData.length;
    if (size < oldCapacity) {
            elementData = Arrays.copyOf(elementData, size);
    }
}

使用ArrayList的註意事項:

1.ArrayList是基於數組的方式實現的,可以通過下標索引直接查找到指定位置的元素,因此查找效率高,但每次插入或刪除元素,就要大量地移動元素,插入刪除元素的效率低。
2.ArrayList在插入元素時,可能會進行數組的擴容,但是在刪除元素時卻不會減小數組的容量,如果希望減小數組的容量,可使用trimToSize方法,在查找元素要遍曆數組時,對非null元素使用equals方法,對null元素使用==。
3.擴充容量的方法ensureCapacity。ArrayList在每次增加元素(可能是1個,也可能是一組)時,都要調用該方法來確保足夠的容量。當 容量不足以容納當前的元素個數時,就設置新的容量為舊的容量的1.5倍加1,如果設置後的新容量還不夠,則直接新容量設置為傳入的參數(也就是所需的容 量),而後用Arrays.copyof()方法將元素拷貝到新的數組。從中可以看出,當容量不夠時,每次增加元素,都要將原來的元 素拷貝到一個新的數組中,非常之耗時,也因此建議在事先能確定元素數量的情況下,才使用ArrayList,否則建議使用LinkedList
4.ArrayList不是線程安全的。

 

接著我們看下LinkedList,LinkedList是基於雙向鏈表的方式來實現的,雙向鏈表就是集合中的每個元素都知道其前面的一個元素的位置和它後的一個元素位置。

在LinkedList中,使用一個內部類Entry來表示集合中的節點,元素的值賦值給element屬性,節點的next屬性指向下一個節點,節點的previous屬性指向前一個節點。

private static class Entry<E> {
//element存放數據 E element;
//next屬性指向當前節點的下一個節點 Entry
<E> next;
  //previous屬性指向當前節點的上一個節點 Entry
<E> previous; Entry(E element, Entry<E> next, Entry<E> previous) { this.element = element; this.next = next; this.previous = previous; } }

 

/**
*維護了一個header節點,header節點的element屬性為null,previouse屬性為null,next屬性為null,並且header節點是不存儲數據的
*/
private transient Entry<E> header = new Entry<E>(null, null, null);
//size表示集合包含的元素個數
private transient int size = 0;

/**
* 構造一個空的集合,將header節點的next屬性、previous屬性都指向header節點,這樣形成了一個雙向鏈表的閉環
*/
public LinkedList() {
     header.next = header.previous = header;
}

新增操作

/**
*追加指定的元素e到集合尾部,其效果和方法 addLast(E e)是一致的,都是調用addBefore(e,header)方法。
*/
public boolean add(E e) {
    addBefore(e, header);
    return true;
}

/**
*將數據e 插入到元素entry前面(private級別,LinkedList內部調用,意味著不能直接在自己的應用程式中調用此方法,但是可以利用反射API來間接的調用(好想沒必要這樣做))
*/
private Entry<E> addBefore(E e, Entry<E> entry) {
    //創建一個新節點newEntry,newEntry.element屬性設置為e,newEntry.next屬性設置為entry,newEntry.previous屬性設置為entry.previous
    Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
    ///將newEntry.previous節點的next屬性指向newEntry本身
    newEntry.previous.next = newEntry;
    //將newEntry.next節點的previous屬性指向newEntry本身
    newEntry.next.previous = newEntry;
    //將集合大小size + 1
    size++;
    //集合大小影響次數modCount + 1
    modCount++;
  //返回新創建的節點
    return newEntry;
}

下麵我們看一下新增一個節點,在集合中的直接視圖情況:

 假設我們一開始創建一個LinkedList時,只有一個header節點(不存儲數據的),如下圖所示:

有一個元素A1插入到了header的前面。

 

 現在要插入一個元素A2,在執行完下麵代碼後,

Entry<E> newEntry = new Entry<E>(A2, header, header.previous);  //將newEntry的next屬性指向header節點,newEntry.previous屬性指向header.previous指向的節點(A1);

newEntry.previous.next = newEntry;  //將newEntry.previous節點(A1)的next屬性指向newEntry,即將A1.previous屬性指向A2。

newEntry.next.previous = newEntry;//將newEntry.next節點(header)的previous屬性指向newEntry,即將header.previous屬性指向A2.

圖形變成了下麵的樣子:

 

 我們看一下LinkedList的一個帶參的構造函數:

/**
*以指定集合c的迭代器返回的節點順序,構造一個包含指定集合c中所有元素的集合
*/
public LinkedList(Collection<? extends E> c) {
    //這裡this()調用了LinkedList的無參構造器,使header.next=header.previous=header,即此時僅有一個header節點
    this();
    //調用addAll(c)方法
    addAll(c);
}

/**
*將指定集合c中所有的元素,按照其迭代器返回的順序全部追加到集合的結尾。
*/
public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
}

/**
*將指定集合c中所有的元素,按照其迭代器返回的順序,從指定的位置index開始全部插入到集合中。
*/
public boolean addAll(int index, Collection<? extends E> c) {
       //對參數index做邊界檢查,無效拋出IndexOutOfBoundsException
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
       //將集合c 轉換為數組
        Object[] a = c.toArray();
       //獲取數組的長度
        int numNew = a.length;
       //若數組的長度為0,說明數組是空的,沒有可以插入的元素,則直接返回false
        if (numNew==0)
            return false;
       //程式走到這,說明有可以插入的數據了,集合大小肯定會改變,因此modCount+1
       modCount++;
       //如果入參index==size,則將header節點設置為後繼節點successor,否則調用entry(int)方法求出位置index的節點,並將該節點設置為後繼節點successor.
        Entry<E> successor = (index==size ? header : entry(index));
      //獲取後繼節點successor的前驅節點predecessor
        Entry<E> predecessor = successor.previous;
      //迴圈數組中的元素,進入數據的插入
       for (int i=0; i<numNew; i++) {
            //創建一個新節點e,將數組a的第i個元素a[i]作為新節點e的element屬性,successor作為e的next節點,predescessor作為e的previous節點
            Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);
           //將predecessor的next屬性指向新節點e
            predecessor.next = e;
          //由於還要進行後續的插入,因此將新節點e設置為前驅節點predecessor,以便繼續迴圈
            predecessor = e;
        }
       //在迴圈數組中的元素插入完成後,將sucessor.previous屬性指向predecessor節點,此時已經完成了雙向鏈表的閉環了。
        successor.previous = predecessor;
      //將集合大小size 增加numNew個
        size += numNew;
        return true;
}

/**
*返回指定位置index的節點
*/
private Entry<E> entry(int index) {
    //對參數index做邊界檢查,無效拋出IndexOutOfBoundsException
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        //將頭結點header設置為e
        Entry<E> e = header;
       //如果入參index小於數組大小size的一半,即index< size/2的話,則從前往後查找(從下標為0開始,到index),否則從後往前查找(從下標為size開始,到index+1)
        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;
}    

 

 其他的添加操作:

/**
*追加指定元素e到集合的結尾,效果和調用add(E e)方法是一樣的(都是調用方法addBefore(e,header)),只是該方法沒返回值
*/
public void addLast(E e) {
    addBefore(e, header);
}

/**
*將指定元素e插入到集合的開始位置,調用方法addBefore(e,header.next)實現
*/
public void addFirst(E e) {
       //這裡插入在header.next節點的前面,因此可以認為是集合的開始位置
    addBefore(e, header.next);
}

/**
*在指定位置index處插入指定的元素e
*/
public void add(int index, E element) {
       //若index== size,即要追加到集合的結尾處,即插入到header之前;否則調用entry(int)方法獲取index處的節點,插入到該節點之前
        addBefore(element, (index==size ? header : entry(index)));
}

/**
*將元素element設值到位置為index處(即將原index處的值替換為element),並返回原來index處的值
*/
public E set(int index, E element) {
        Entry<E> e = entry(index);
        E oldVal = e.element;
        e.element = element;
        return oldVal;
}

/**
*追加元素e到集合結尾
*/
public boolean offer(E e) {
        return add(e);
}

/**
*將元素e插入集合的開始位置,調用addFirst(E e)方法實現
*/
public boolean offerFirst(E e) {
        addFirst(e);
        return true;
}

/**
*將元素e插入集合的結束位置,調用addLast(E e)方法實現
*/
public boolean offerLast(E e) {
        addLast(e);
        return true;
}

/**
*將元素e推入(push)進入棧 (LinkedList也可以當做棧使用)
*/
public void push(E e) {
        addFirst(e);
}

 

 

 刪除操作:

/**
*刪除指定元素在集合中的第一個出現的節點(下標index最小的)
*/
public boolean remove(Object o) {
        //分為兩種情況:1.入參o 為null,使用== 比較 2.入參o不為null,使用equals比較
       
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 下麵以windows平臺和Aptana Studio為例,介紹XDdebug的使用。 1)下載php的XDebug擴展.dll文件,官網下載地址是https://xdebug.org/download.php,可以根據實際php運行系統架構、VC版本和線程安全情況下載。 2)將下載的.dll文件複製
  • 前不久,有人來我們公司面試,我們的經理問道了這個問題,我也是一知半解,所以就去百度了一番。 其實區別很簡單的,舉個例子大家就會明白的。寫一句SQL-例如:select * from user_role where user_code = "100"; 這句話而言,需要寫成 select * from
  • 抽象的定義:抽象是把多個事物的共性的內容抽取出來,本質就是把我們關註的內容抽取出來。(比如:寶馬,賓士都屬於汽車,汽車是抽象出來的概念); 抽象類:Java中可以定義沒有方法體的方法,該方法由其子類來具體的實現。該沒有方法體的方法我們稱之為抽象方法,含有抽象方法的類我們稱之為抽象類; 抽象方法特點:
  • 1.標識符包、類、方法、參數和變數的名稱。大小寫字母、數字、_和$符號的組合,不以數字開始,不能使關鍵字,不能包括分隔符和換行。(嚴格區分大小寫,最大長度255個字元) 2.字面量 某種類型的值(具體的值) 3.註釋不能執行的文字,多用於解釋,有單行註釋//...,多行註釋/*...*/和文檔註釋/
  • ThinkPHP目錄如下,Application顧名思義就是應用的意思(我們的代碼放在這裡),Public就是公共文件的意思(主要放JS CSS 等前端資源文件),ThinkPHP文件是框架的核心包(我們一般不要操作它)。意思就是我們搞後臺的人員寫代碼應該寫在Application的目錄下 第二步,
  • 剛開始學習python變成, 這勉強算是第一個博客吧, 主要記錄了一下 字元串 中的方法, 不太準確,或者是錯誤的地方, 請大家指點 str1 = "GooGle" str2 = "baidu" #print("Google的類型是 %s \n" % type(str1)) #Google的類型是 ...
  • 今天看js高級編程form表單這一章,看著書上的例子敲代碼的時候出現了一點問題,什麼問題先不說,先看這段代碼? 代碼就是上面這個樣子的,很簡單的一個表單裡面有一組radio標簽,要實現的效果也就是通過表單拿到這一組radio標簽,但是我按照上面的寫法打出來之後控制台卻一直報錯,錯誤信息如下: 意思就
  • 隨著公司的規模及項目的增多,會有一種透明傳輸的需求,而透明傳輸的這一層就用來做許可權控制,灰度發佈,流量統計。 實現透傳需要註意的幾點: 1.Spring MVC實現url通配,後端服務的url各式各樣,並不能按照你所設想的長度,so,通配符能解決這個問題。 2.body流解析,POST/PUT/PA
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...