1. 程式人生 > >ArrayList與linkedList面試事項

ArrayList與linkedList面試事項

ArrayList 和linkedList 也是面試中經常遇到的問題,也是平時開發中最常用的list,瞭解這兩個的特性並恰當區分使用,可以一定程度上提高程式碼的執行效率。今個記一下這兩個的使用方法,並總結區別。

ArrayList

arraylist就是一個數組,一個動態陣列,可以動態改變元素,自定義陣列大小。實現了RandomAcess,Cloneable、Serializable介面。

一、先簡單看一下構造方法:

1.public ArrayList();

2.public ArrayList(ICollection);

3.public ArrayList(int);

第一種:預設的構造器,初始化一個容量為10的陣列。

第二種:傳入一個實現ICollection的物件,並將傳入物件中的元素賦值給新建的list;

第三種:自定義陣列的容量。

二、Arraylist中的方法

其中有get、set、add、indexof,contains,clone、size、toarray、isEmpty、sort等方法的使用,我覺得不是什麼難點,api介紹很清楚,在後續比較與linkedlist區別時會介紹一些,現在稍微總結一下:

1.當add方法一直新增,知道超過現有容量大小時,arraylist重新設定的容量為(原始容量*3)/2+1,也就是原本容量增加1/2

2.arraylist實現了Cloneable介面,中心就是其中的clone方法。可以將此物件的全部元素複製到另一個數組中。

3.arraylist實現Serializable介面,是可序列化的,當讀寫的時候,會容量先行,即:寫入的時候,先寫入容量,在放置元素;讀取時,先讀取容量,在去除元素。需要注意的是,可序列化只是一個標誌,意味著它可以被序列化,並沒有實際性的方法、欄位,但是這不代表沒有意義,因為在上述讀寫過程中,就會依次進行。我還沒有了解深入,但事實就是這樣。後續在補充吧。

三、ArrayList的遍歷

談到陣列,集合。免不了使用遍歷,

有三種方法可以遍歷ArrayList;

1.迭代器

部分程式碼:

Iterator iter = list.iterator();
while (iter.hasNext()) {
    value = (Integer)iter.next();


}

2.隨機訪問

for (int i=0; i<list.size; i++) {
    value = (Integer)list.get(i);        

}

3.foreach

for (Integer integ:list) {
    value = integ;

}

測試一下,就能知道他們之間的效率 隨機訪問>foreach>迭代器。這算是ArrayList的最大優點。

LinkedList

LinkedList是一個雙向連結串列,可以被當做堆疊、佇列或者雙向佇列。實現了list、Deque、Cloneable、Serializable介面。

一、為數不多的三個屬性:

1、size,記錄當前list有多少節點。

2、first,代表當前第一個節點。

3、last,代表當前最後一個節點。

後面的方法都與這仨屬性息息相關。

方法

、構造方法:

1.public LinkedList() {

}

2.

public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);

}

只有兩個,與arraylist中對應的兩個用法相同。

三、類中方法

1.add

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++;
}
此方法就是將新的元素放到連結串列最後,長度加1,修改次數加1;

2.帶參add

public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
linkBefore(element, node(index));
}
很清晰,不贅述。

3.get

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}
Node<E> node(int index) {
    // assert isElementIndex(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;
    }
}
首先判斷有沒有超出索引長度。然後和長度的1/2對比,小於就從頭遍歷,大於就從尾部遍歷。

linkedlist不能快速訪問,只能挨個遍歷,為了儘可能少的遍歷,判斷從頭部開始還是尾部開始是個很好的方法

4.remove

public E remove() {
    return removeFirst();
}
public E removeFirst() {
    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;
    f.item = null;
    f.next = null; // help GC
first = next;
    if (next == null)
        last = null;
    else
next.prev = null;
    size--;
    modCount++;
    return element;
}
即:remove就是removefirst,將第一個節點設為null,並將全域性變數first設定為當前節點的下一個節點。長度減一,修改次數加一

5.removelast

private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
final E element = l.item;
    final Node<E> prev = l.prev;
    l.item = null;
    l.prev = null; // help GC
last = prev;
    if (prev == null)
        first = null;
    else
prev.next = null;
    size--;
    modCount++;
    return element;
}
將最後一個節點設為null,將原本最後一個節點的上一個節點設為全域性變數last,長度減一,修改次數加一。

6.remove(object o)

public boolean remove(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

從first到last迴圈遍歷,如果引數和當前元素相等,呼叫unlink(下面解釋此方法)

7.remove(int index)

public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}
Node<E> node(int index) {
    // assert isElementIndex(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;
    }
}
先呼叫node方法,找到索引對應的值,之後和remove(object o)相同
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;
    }

    x.item = null;
    size--;
    modCount++;
    return element;
}
這個方法的思路是獲取要移除節點的上一節點和下一節點,
如果上一節點是空,則list的first節點為當前節點的下一節點,
如果下一節點為空,list的last節點為當前節點的上一節點。
如果都不是,把當前節點設為null,這樣當前節點的上一節點指向當前節點的下一節點。

8.toarray

public Object[] toArray() {
    Object[] result = new Object[size];
    int i = 0;
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;
    return result;
}

遍歷當前list,並挨個賦值到新的陣列中。

區別

從上面的分析已經可以大致看出兩者的區別了,總結一下:

linkedlist是基於連結串列的list,從大量的成員方法也能看出,新增,刪除佔了大比重,相比Arraylist的增刪,linkedlist更高效,因為沒有list容量的問題,不需要再新增時判斷容量是否超出;刪除時直接刪除元素,上一節點會指向下一節點,而arraylist刪除指定元素,後面的所有元素都必須向前移動相應位置。

ArrayList的快速訪問是一個優勢,從上面linkedlist的方法中可以看到,遍歷佔據了大量的份額,任何的查詢,大部分的刪除都得在真正的行為開始前進行所有元素的遍歷。而作為陣列的arrayList,可以通過索引,直接訪問到對應的元素。

總而言之,arraylist更適合讀取,linkedlist更適合增刪。