面試官:兄弟,說說 ArrayList 和 LinkedList 有什麼區別

2021-03-02 腳本之家

ArrayList 和 LinkedList 有什麼區別,是面試官非常喜歡問的一個問題。可能大部分小夥伴和我一樣,能回答出「ArrayList 是基於數組實現的,LinkedList 是基於雙向鍊表實現的。」

關於這一點,我之前的文章裡也提到過了。但說實話,這樣蒼白的回答並不能令面試官感到滿意,他還想知道的更多。

那假如小夥伴們繼續做出下面這樣的回答:

「ArrayList 在新增和刪除元素時,因為涉及到數組複製,所以效率比 LinkedList 低,而在遍歷的時候,ArrayList 的效率要高於 LinkedList。」

面試官會感到滿意嗎?我只能說,如果面試官比較仁慈的話,他可能會讓我們回答下一個問題;否則的話,他會讓我們回家等通知,這一等,可能意味著杳無音訊了。

為什麼會這樣呢?為什麼為什麼?回答的不對嗎?

暴躁的小夥伴請喝口奶茶冷靜一下。冷靜下來後,請隨我來,讓我們一起肩並肩、手拉手地深入地研究一下 ArrayList 和 LinkedList 的數據結構、實現原理以及源碼,可能神秘的面紗就揭開了。


ArrayList 是如何實現的?

ArrayList 實現了 List 接口,繼承了 AbstractList 抽象類,底層是基於數組實現的,並且實現了動態擴容。

public class ArrayList<E> extends AbstractList<E>        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{    private static final int DEFAULT_CAPACITY = 10;    transient Object[] elementData;    private int size;}

ArrayList 還實現了 RandomAccess 接口,這是一個標記接口:

public interface RandomAccess {}

內部是空的,標記「實現了這個接口的類支持快速(通常是固定時間)隨機訪問」。快速隨機訪問是什麼意思呢?就是說不需要遍歷,就可以通過下標(索引)直接訪問到內存地址。

public E get(int index) {    Objects.checkIndex(index, size);    return elementData(index);}E elementData(int index) {    return (E) elementData[index];}

ArrayList 還實現了 Cloneable 接口,這表明 ArrayList 是支持拷貝的。ArrayList 內部的確也重寫了 Object 類的 clone() 方法。

public Object clone() {    try {        ArrayList<?> v = (ArrayList<?>) super.clone();        v.elementData = Arrays.copyOf(elementData, size);        v.modCount = 0;        return v;    } catch (CloneNotSupportedException e) {        // this shouldn't happen, since we are Cloneable        throw new InternalError(e);    }}

ArrayList 還實現了 Serializable 接口,同樣是一個標記接口:

public interface Serializable {}

內部也是空的,標記「實現了這個接口的類支持序列化」。序列化是什麼意思呢?Java 的序列化是指,將對象轉換成以字節序列的形式來表示,這些字節序中包含了對象的欄位和方法。序列化後的對象可以被寫到資料庫、寫到文件,也可用於網絡傳輸。

眼睛雪亮的小夥伴可能會注意到,ArrayList 中的關鍵欄位 elementData 使用了 transient 關鍵字修飾,這個關鍵字的作用是,讓它修飾的欄位不被序列化。

這不前後矛盾嗎?一個類既然實現了 Serilizable 接口,肯定是想要被序列化的,對吧?那為什麼保存關鍵數據的 elementData 又不想被序列化呢?

這還得從 「ArrayList 是基於數組實現的」開始說起。大家都知道,數組是定長的,就是說,數組一旦聲明了,長度(容量)就是固定的,不能像某些東西一樣伸縮自如。這就很麻煩,數組一旦裝滿了,就不能添加新的元素進來了。

ArrayList 不想像數組這樣活著,它想能屈能伸,所以它實現了動態擴容。一旦在添加元素的時候,發現容量用滿了 s == elementData.length,就按照原來數組的 1.5 倍(oldCapacity >> 1)進行擴容。擴容之後,再將原有的數組複製到新分配的內存地址上 Arrays.copyOf(elementData, newCapacity)。

private void add(E e, Object[] elementData, int s) {    if (s == elementData.length)        elementData = grow();    elementData[s] = e;    size = s + 1;}
private Object[] grow() { return grow(size + 1);}
private Object[] grow(int minCapacity) { int oldCapacity = elementData.length; if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { int newCapacity = ArraysSupport.newLength(oldCapacity, minCapacity - oldCapacity, /* minimum growth */ oldCapacity >> 1 /* preferred growth */); return elementData = Arrays.copyOf(elementData, newCapacity); } else { return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)]; }}

動態擴容意味著什麼?大傢伙想一下。嗯,還是我來告訴大家答案吧,有點迫不及待。

意味著數組的實際大小可能永遠無法被填滿的,總有多餘出來空置的內存空間。

比如說,默認的數組大小是 10,當添加第 11 個元素的時候,數組的長度擴容了 1.5 倍,也就是 15,意味著還有 4 個內存空間是閒置的,對吧?

序列化的時候,如果把整個數組都序列化的話,是不是就多序列化了 4 個內存空間。當存儲的元素數量非常非常多的時候,閒置的空間就非常非常大,序列化耗費的時間就會非常非常多。

於是,ArrayList 做了一個愉快而又聰明的決定,內部提供了兩個私有方法 writeObject 和 readObject 來完成序列化和反序列化。

private void writeObject(java.io.ObjectOutputStream s)        throws java.io.IOException {    // Write out element count, and any hidden stuff    int expectedModCount = modCount;    s.defaultWriteObject();
// Write out size as capacity for behavioral compatibility with clone() s.writeInt(size);
// Write out all elements in the proper order. for (int i=0; i<size; i++) { s.writeObject(elementData[i]); }
if (modCount != expectedModCount) { throw new ConcurrentModificationException(); }}

從 writeObject 方法的源碼中可以看得出,它使用了 ArrayList 的實際大小 size 而不是數組的長度(elementData.length)來作為元素的上限進行序列化。

此處應該有掌聲啊!不是為我,為 Java 源碼的作者們,他們真的是太厲害了,可以用兩個詞來形容他們——殫精竭慮、精益求精。


LinkedList 是如何實現的?

LinkedList 是一個繼承自 AbstractSequentialList 的雙向鍊表,因此它也可以被當作堆棧、隊列或雙端隊列進行操作。

public class LinkedList<E>    extends AbstractSequentialList<E>    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{    transient int size = 0;    transient Node<E> first;    transient Node<E> last;}

LinkedList 內部定義了一個 Node 節點,它包含 3 個部分:元素內容 item,前引用 prev 和後引用 next。代碼如下所示:

private static class Node<E> {    E item;    LinkedList.Node<E> next;    LinkedList.Node<E> prev;
Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) { this.item = element; this.next = next; this.prev = prev; }}

LinkedList 還實現了 Cloneable 接口,這表明 LinkedList 是支持拷貝的。

LinkedList 還實現了 Serializable 接口,這表明 LinkedList 是支持序列化的。眼睛雪亮的小夥伴可能又注意到了,LinkedList 中的關鍵欄位 size、first、last 都使用了 transient 關鍵字修飾,這不又矛盾了嗎?到底是想序列化還是不想序列化?

答案是 LinkedList 想按照自己的方式序列化,來看它自己實現的 writeObject() 方法:

private void writeObject(java.io.ObjectOutputStream s)        throws java.io.IOException {    // Write out any hidden serialization magic    s.defaultWriteObject();
// Write out size s.writeInt(size);
// Write out all elements in the proper order. for (LinkedList.Node<E> x = first; x != null; x = x.next) s.writeObject(x.item);}

發現沒?LinkedList 在序列化的時候只保留了元素的內容 item,並沒有保留元素的前後引用。這樣就節省了不少內存空間,對吧?

那有些小夥伴可能就疑惑了,只保留元素內容,不保留前後引用,那反序列化的時候怎麼辦?

private void readObject(java.io.ObjectInputStream s)        throws java.io.IOException, ClassNotFoundException {    // Read in any hidden serialization magic    s.defaultReadObject();
// Read in size int size = s.readInt();
// Read in all elements in the proper order. for (int i = 0; i < size; i++) linkLast((E)s.readObject());}
void linkLast(E e) { final LinkedList.Node<E> l = last; final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++;}

注意 for 循環中的 linkLast() 方法,它可以把鍊表重新連結起來,這樣就恢復了鍊表序列化之前的順序。很妙,對吧?

和 ArrayList 相比,LinkedList 沒有實現 RandomAccess 接口,這是因為 LinkedList 存儲數據的內存地址是不連續的,所以不支持隨機訪問。


ArrayList 和 LinkedList 新增元素時究竟誰快?

前面我們已經從多個維度了解了 ArrayList 和 LinkedList 的實現原理和各自的特點。那接下來,我們就來聊聊 ArrayList 和 LinkedList 在新增元素時究竟誰快?

1)ArrayList

ArrayList 新增元素有兩種情況,一種是直接將元素添加到數組末尾,一種是將元素插入到指定位置。

添加到數組末尾的源碼:

public boolean add(E e) {    modCount++;    add(e, elementData, size);    return true;}
private void add(E e, Object[] elementData, int s) { if (s == elementData.length) elementData = grow(); elementData[s] = e; size = s + 1;}

很簡單,先判斷是否需要擴容,然後直接通過索引將元素添加到末尾。

插入到指定位置的源碼:

public void add(int index, E element) {    rangeCheckForAdd(index);    modCount++;    final int s;    Object[] elementData;    if ((s = size) == (elementData = this.elementData).length)        elementData = grow();    System.arraycopy(elementData, index,            elementData, index + 1,            s - index);    elementData[index] = element;    size = s + 1;}

先檢查插入的位置是否在合理的範圍之內,然後判斷是否需要擴容,再把該位置以後的元素複製到新添加元素的位置之後,最後通過索引將元素添加到指定的位置。這種情況是非常傷的,性能會比較差。

2)LinkedList

LinkedList 新增元素也有兩種情況,一種是直接將元素添加到隊尾,一種是將元素插入到指定位置。

添加到隊尾的源碼:

public boolean add(E e) {    linkLast(e);    return true;}void linkLast(E e) {    final LinkedList.Node<E> l = last;    final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null);    last = newNode;    if (l == null)        first = newNode;    else        l.next = newNode;    size++;    modCount++;}

先將隊尾的節點 last 存放到臨時變量 l 中(不是說不建議使用 I 作為變量名嗎?Java 的作者們明知故犯啊),然後生成新的 Node 節點,並賦給 last,如果 l  為 null,說明是第一次添加,所以 first 為新的節點;否則將新的節點賦給之前 last 的 next。

插入到指定位置的源碼:

public void add(int index, E element) {    checkPositionIndex(index);
if (index == size) linkLast(element); else linkBefore(element, node(index));}LinkedList.Node<E> node(int index) { // assert isElementIndex(index);
if (index < (size >> 1)) { LinkedList.Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { LinkedList.Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; }}void linkBefore(E e, LinkedList.Node<E> succ) { // assert succ != null; final LinkedList.Node<E> pred = succ.prev; final LinkedList.Node<E> newNode = new LinkedList.Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++;}

先檢查插入的位置是否在合理的範圍之內,然後判斷插入的位置是否是隊尾,如果是,添加到隊尾;否則執行 linkBefore() 方法。

在執行 linkBefore() 方法之前,會調用 node() 方法查找指定位置上的元素,這一步是需要遍歷 LinkedList 的。如果插入的位置靠前前半段,就從隊頭開始往後找;否則從隊尾往前找。也就是說,如果插入的位置越靠近 LinkedList 的中間位置,遍歷所花費的時間就越多。

找到指定位置上的元素(succ)之後,就開始執行 linkBefore() 方法了,先將 succ 的前一個節點(prev)存放到臨時變量 pred 中,然後生成新的 Node 節點(newNode),並將 succ 的前一個節點變更為 newNode,如果 pred 為 null,說明插入的是隊頭,所以 first 為新節點;否則將 pred 的後一個節點變更為 newNode。

經過源碼分析以後,小夥伴們是不是在想:「好像 ArrayList 在新增元素的時候效率並不一定比 LinkedList 低啊!」

當兩者的起始長度是一樣的情況下:

public class ArrayListTest {    public static void addFromHeaderTest(int num) {        ArrayList<String> list = new ArrayList<String>(num);        int i = 0;
long timeStart = System.currentTimeMillis();
while (i < num) { list.add(0, i + "沉默王二"); i++; } long timeEnd = System.currentTimeMillis();
System.out.println("ArrayList從集合頭部位置新增元素花費的時間" + (timeEnd - timeStart)); }}
/** * @author 微信搜「沉默王二」,回復關鍵字 PDF */public class LinkedListTest { public static void addFromHeaderTest(int num) { LinkedList<String> list = new LinkedList<String>(); int i = 0; long timeStart = System.currentTimeMillis(); while (i < num) { list.addFirst(i + "沉默王二"); i++; } long timeEnd = System.currentTimeMillis();
System.out.println("LinkedList從集合頭部位置新增元素花費的時間" + (timeEnd - timeStart)); }}

num 為 10000,代碼實測後的時間如下所示:

ArrayList從集合頭部位置新增元素花費的時間595LinkedList從集合頭部位置新增元素花費的時間15

ArrayList 花費的時間比 LinkedList 要多很多。

public class ArrayListTest {    public static void addFromMidTest(int num) {        ArrayList<String> list = new ArrayList<String>(num);        int i = 0;
long timeStart = System.currentTimeMillis(); while (i < num) { int temp = list.size(); list.add(temp / 2 + "沉默王二"); i++; } long timeEnd = System.currentTimeMillis();
System.out.println("ArrayList從集合中間位置新增元素花費的時間" + (timeEnd - timeStart)); }}
public class LinkedListTest { public static void addFromMidTest(int num) { LinkedList<String> list = new LinkedList<String>(); int i = 0; long timeStart = System.currentTimeMillis(); while (i < num) { int temp = list.size(); list.add(temp / 2, i + "沉默王二"); i++; } long timeEnd = System.currentTimeMillis();
System.out.println("LinkedList從集合中間位置新增元素花費的時間" + (timeEnd - timeStart)); }}

num 為 10000,代碼實測後的時間如下所示:

ArrayList從集合中間位置新增元素花費的時間1LinkedList從集合中間位置新增元素花費的時間101

ArrayList 花費的時間比 LinkedList 要少很多很多。

public class ArrayListTest {    public static void addFromTailTest(int num) {        ArrayList<String> list = new ArrayList<String>(num);        int i = 0;
long timeStart = System.currentTimeMillis();
while (i < num) { list.add(i + "沉默王二"); i++; }
long timeEnd = System.currentTimeMillis();
System.out.println("ArrayList從集合尾部位置新增元素花費的時間" + (timeEnd - timeStart)); }}
public class LinkedListTest { public static void addFromTailTest(int num) { LinkedList<String> list = new LinkedList<String>(); int i = 0; long timeStart = System.currentTimeMillis(); while (i < num) { list.add(i + "沉默王二"); i++; } long timeEnd = System.currentTimeMillis();
System.out.println("LinkedList從集合尾部位置新增元素花費的時間" + (timeEnd - timeStart)); }}

num 為 10000,代碼實測後的時間如下所示:

ArrayList從集合尾部位置新增元素花費的時間69LinkedList從集合尾部位置新增元素花費的時間193

ArrayList 花費的時間比 LinkedList 要少一些。

這樣的結論和預期的是不是不太相符?ArrayList 在添加元素的時候如果不涉及到擴容,性能在兩種情況下(中間位置新增元素、尾部新增元素)比 LinkedList 好很多,只有頭部新增元素的時候比 LinkedList 差,因為數組複製的原因。

當然了,如果涉及到數組擴容的話,ArrayList 的性能就沒那麼可觀了,因為擴容的時候也要複製數組。


ArrayList 和 LinkedList 刪除元素時究竟誰快?

1)ArrayList

ArrayList 刪除元素的時候,有兩種方式,一種是直接刪除元素(remove(Object)),需要直先遍歷數組,找到元素對應的索引;一種是按照索引刪除元素(remove(int))。

public boolean remove(Object o) {    final Object[] es = elementData;    final int size = this.size;    int i = 0;    found: {        if (o == null) {            for (; i < size; i++)                if (es[i] == null)                    break found;        } else {            for (; i < size; i++)                if (o.equals(es[i]))                    break found;        }        return false;    }    fastRemove(es, i);    return true;}public E remove(int index) {    Objects.checkIndex(index, size);    final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index]; fastRemove(es, index);
return oldValue;}

但從本質上講,都是一樣的,因為它們最後調用的都是 fastRemove(Object, int) 方法。

private void fastRemove(Object[] es, int i) {    modCount++;    final int newSize;    if ((newSize = size - 1) > i)        System.arraycopy(es, i + 1, es, i, newSize - i);    es[size = newSize] = null;}

從源碼可以看得出,只要刪除的不是最後一個元素,都需要數組重組。刪除的元素位置越靠前,代價就越大。

2)LinkedList

LinkedList 刪除元素的時候,有四種常用的方式:

public E remove(int index) {    checkElementIndex(index);    return unlink(node(index));}

先檢查索引,再調用 node(int) 方法( 前後半段遍歷,和新增元素操作一樣)找到節點 Node,然後調用 unlink(Node) 解除節點的前後引用,同時更新前節點的後引用和後節點的前引用:

   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; }

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

也是先前後半段遍歷,找到要刪除的元素後調用 unlink(Node)。

public E removeFirst() {    final LinkedList.Node<E> f = first;    if (f == null)        throw new NoSuchElementException();    return unlinkFirst(f);}private E unlinkFirst(LinkedList.Node<E> f) {    // assert f == first && f != null;    final E element = f.item;    final LinkedList.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;}

刪除第一個節點就不需要遍歷了,只需要把第二個節點更新為第一個節點即可。

刪除最後一個節點和刪除第一個節點類似,只需要把倒數第二個節點更新為最後一個節點即可。

可以看得出,LinkedList 在刪除比較靠前和比較靠後的元素時,非常高效,但如果刪除的是中間位置的元素,效率就比較低了。

這裡就不再做代碼測試了,感興趣的小夥伴可以自己試試,結果和新增元素保持一致:

從集合頭部刪除元素時,ArrayList 花費的時間比 LinkedList 多很多;

從集合中間位置刪除元素時,ArrayList 花費的時間比 LinkedList 少很多;

從集合尾部刪除元素時,ArrayList 花費的時間比 LinkedList 少一點。

我本地的統計結果如下所示,小夥伴們可以作為參考:

ArrayList從集合頭部位置刪除元素花費的時間380LinkedList從集合頭部位置刪除元素花費的時間4ArrayList從集合中間位置刪除元素花費的時間381LinkedList從集合中間位置刪除元素花費的時間5922ArrayList從集合尾部位置刪除元素花費的時間8LinkedList從集合尾部位置刪除元素花費的時間12


ArrayList 和 LinkedList 遍曆元素時究竟誰快?

1)ArrayList

遍歷 ArrayList 找到某個元素的話,通常有兩種形式:

public E get(int index) {    Objects.checkIndex(index, size);    return elementData(index);}

由於 ArrayList 是由數組實現的,所以根據索引找元素非常的快,一步到位。

public int indexOf(Object o) {    return indexOfRange(o, 0, size);}
int indexOfRange(Object o, int start, int end) { Object[] es = elementData; if (o == null) { for (int i = start; i < end; i++) { if (es[i] == null) { return i; } } } else { for (int i = start; i < end; i++) { if (o.equals(es[i])) { return i; } } } return -1;}

根據元素找索引的話,就需要遍歷整個數組了,從頭到尾依次找。

2)LinkedList

遍歷 LinkedList 找到某個元素的話,通常也有兩種形式:

public E get(int index) {    checkElementIndex(index);    return node(index).item;}

既然需要調用 node(int) 方法,就意味著需要前後半段遍歷了。

public int indexOf(Object o) {    int index = 0;    if (o == null) {        for (LinkedList.Node<E> x = first; x != null; x = x.next) {            if (x.item == null)                return index;            index++;        }    } else {        for (LinkedList.Node<E> x = first; x != null; x = x.next) {            if (o.equals(x.item))                return index;            index++;        }    }    return -1;}

需要遍歷整個鍊表,和 ArrayList 的 indexOf() 類似。

那在我們對集合遍歷的時候,通常有兩種做法,一種是使用 for 循環,一種是使用迭代器(Iterator)。

如果使用的是 for 循環,可想而知 LinkedList 在 get 的時候性能會非常差,因為每一次外層的 for 循環,都要執行一次 node(int) 方法進行前後半段的遍歷。

LinkedList.Node<E> node(int index) {    // assert isElementIndex(index);
if (index < (size >> 1)) { LinkedList.Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { LinkedList.Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; }}

那如果使用的是迭代器呢?

LinkedList<String> list = new LinkedList<String>();for (Iterator<String> it = list.iterator(); it.hasNext();) {    it.next();}

迭代器只會調用一次 node(int) 方法,在執行 list.iterator() 的時候:先調用 AbstractSequentialList 類的 iterator() 方法,再調用 AbstractList 類的 listIterator() 方法,再調用 LinkedList 類的 listIterator(int) 方法,如下圖所示。

最後返回的是 LinkedList 類的內部私有類 ListItr 對象:

public ListIterator<E> listIterator(int index) {    checkPositionIndex(index);    return new LinkedList.ListItr(index);}
private class ListItr implements ListIterator<E> { private LinkedList.Node<E> lastReturned; private LinkedList.Node<E> next; private int nextIndex; private int expectedModCount = modCount;
ListItr(int index) { // assert isPositionIndex(index); next = (index == size) ? null : node(index); nextIndex = index; }
public boolean hasNext() { return nextIndex < size; }
public E next() { checkForComodification(); if (!hasNext()) throw new NoSuchElementException();
lastReturned = next; next = next.next; nextIndex++; return lastReturned.item; }}

執行 ListItr 的構造方法時調用了一次 node(int) 方法,返回第一個節點。在此之後,迭代器就執行 hasNext() 判斷有沒有下一個,執行 next() 方法下一個節點。

由此,可以得出這樣的結論:遍歷 LinkedList 的時候,千萬不要使用 for 循環,要使用迭代器。

也就是說,for 循環遍歷的時候,ArrayList 花費的時間遠小於 LinkedList;迭代器遍歷的時候,兩者性能差不多。

花了兩天時間,終於肝完了!相信看完這篇文章後,再有面試官問你 ArrayList 和 LinkedList 有什麼區別的話,你一定會胸有成竹地和他扯上半小時。

相關焦點

  • 2017年網友京東Java面試經驗:感覺問的比較基礎,大家都是這樣?
    面試官比較好,說你不能來也很正常,我們來進行一個20分鐘的面試吧。0.HTTP TCP UDP 的區別,具體用在哪些場景。1.說一下 java中的隊列 set map 區別,java裡的數據結構。講講它們的實現。
  • 滴滴Android崗面經分享:面試真題+經驗總結
    java的集合類的介紹我把能想到的全都說了,map和collection的區別,collection下的list和set的區別,list中的arraylist和linkedlist的介紹(如何實現)以及區別。set中的hashset和treeset。map這邊有hashmap和treemap,底層實現。
  • 最新阿里面試回來總結分享
    阿里一面: ArrayList 和 linkedlist 區別。ArrayList 是否會越界。 ArrayList 和 hashset 有何區別。hashset 存的數是有序的麼。
  • 吊打面試官系列:final、finally、finalize 有什麼區別?
    final關鍵字初級回答final ,是修飾符關鍵字。修飾類,表示該類不能在被繼承。修飾方法,表示該方法不能被子類重寫。修飾變量,表示該變量是常量,不能被修改。中高級回答另外,在早期的 Java 實現版本中,會將 final 方法轉為內嵌調用。
  • 【Python面試】 說說Python中xrange和range的區別?
    公眾號新增加了一個欄目,就是每天給大家解答一道Python常見的面試題,反正每天不貪多,一天一題,正好合適,只希望這個面試欄目,給那些正在準備面試的同學,提供一點點幫助!小猿會從最基礎的面試題開始,每天一題。如果參考答案不夠好,或者有錯誤的話,麻煩大家可以在留言區給出自己的意見和討論,大家是要一起學習的 。
  • 網友面試N多家公司,孟哥幫你總結面試題,再也不怕面試了
    需要注意的地方就是不要只講一些業務相關的,面試官不需要知道這些,他只關心你的技術能力是否足夠,通過你的描述知道你使用了什麼技術,再根據相關的技術進行提問。具體的面試問題有:springboot和spring的不同springboot特性,自動裝配怎麼實現的spring用到哪些組件,具體場景spring默認是單例還是多例,全局變量會有什麼問題spring事務傳播行為、隔離級別aop實現方式,jdk動態代理和cglib動態代理區別jdk lambda表達式幾種使用方式
  • 面試官:四兄弟,年齡相乘為12,分別有多大?小夥秒回復,被錄取
    面試官:四兄弟,年齡相乘為12,分別有多大?小夥秒回復,被錄取畢業季到了,大學生們第一次感覺到了就業壓力的強大,這些初涉社會的學子們,都幻想著能進入一家環境好,工資待遇高,工作時間短的單位上班,但面試這一關就有許多人被卡住了。
  • 面試官問你有什麼優勢時,應該怎麼回答?
    1、你是列了好多條,但是,面試官想看看你認為哪幾條,是值得面試拿出來講的優勢,這是看下你的自我認知2、簡歷畢竟是正式文檔,承載信息量有限,一般就一頁或兩頁,聽你仔細說說,可以更全面的了解你的某項優勢,且可以追問,進行核實和更深入判斷3、也順便再看看你的表達能力、邏輯性
  • ArrayList還是LinkedList?性能可差千倍
    作者:沉默王二ArrayList 和 LinkedList 有什麼區別,是面試官非常喜歡問的一個問題。可能大部分小夥伴和我一樣,能回答出「ArrayList 是基於數組實現的,LinkedList 是基於雙向鍊表實現的。」關於這一點,我之前的文章裡也提到過了。但說實話,這樣蒼白的回答並不能令面試官感到滿意,他還想知道的更多。
  • 當面試官問你有什麼優點和缺點的時候,你會怎麼說?
    這次給大家聊聊這個優點和缺點在面試時候的套路哈!相信小夥伴們,在面試的時候,都會被面試官問到過這個問題,每個人的答案肯定都不一樣。但是,基本上很多人都會犯類型的錯誤。一個不小心,你就被面試官給套路了。來自網絡好了,先說優點方面吧,當面試官問你有什麼優點的時候,通常情況下,就是在通過你的描述,看你是否適合招聘的崗位,他是在視圖從你身上找一些和這個崗位匹配的優點。
  • LinkedList給我深深的上了節for增強的課
    這是在群裡看見的一個面試題,網上沒有找到答案,我認為是基本類型和引用類型都可以,還有就是實現了Iterable接口的,比如集合。有知道答案的可以在下面評論下。不勝感激。步入正題:首先看下在數組中有什麼差異
  • 985碩,秋招面試30家企業,怒斬阿里、字節、美團offer
    7.對比其他mq,有什麼區別,怎麼選型?8.項目壓測了嘛?(Jmeter)9.arraylist,linkedlist,hashset區別和使用場景,線程安全?10.hashset為什麼是隨機的?其他兩個是按存入順序的?
  • 半結構化與結構化面試有什麼區別?
    半結構化與結構化面試有什麼區別?半結構化面試是當前面試實踐中應用最廣的一種面試方法,金融系統考試、銀行系統考試、企業招聘考試等都把它作為一種主要方法。其特點是具有雙向溝通性,面試官可以獲得更為豐富、完整和深入的信息,面試形式靈活、問題前後有關聯性、面試時長以及所提問題數量不等。典型題目:1.
  • CopyOnWriteArrayList你都不知道,怎麼拿offer?
    大家對線程安全容器可能最熟悉的就是ConcurrentHashMap了,因為這個容器經常會在面試的時候考查。比如說,一個常見的面試場景:面試官問:「HashMap是線程安全的嗎?如果HashMap線程不安全的話,那有沒有安全的Map容器」3y:「線程安全的Map有兩個,一個是Hashtable,一個是ConcurrentHashMap」面試官繼續問:「那Hashtable和ConcurrentHashMap有什麼區別啊?」
  • 面試官:遠程視頻面試與現場面試的區別,小王回答巧妙,直接錄用
    昨天小王去面試一家同行企業,職位也是所在區域的銷售,人力部門約好了時間,小王如約進行了微信視頻連線面試。面試官開始噓寒問暖了一番,小王也做了簡要的自我介紹。面試官隨後問了家庭背景、經濟狀況以及求職意向和動機之類問題。小王也一一作答了。
  • 面試官:說說自己的優缺點?小夥坦承自己性格急躁,慘遭淘汰!
    面試官:說說自己的優缺點?小夥坦承自己性格急躁,慘遭淘汰!隨著我國教育事業的不斷發展,大學生的人才市場越來越飽和,本科生的學歷已經不算什麼高學歷了,所以在就業問題上,為了得到心儀的工作,學歷不僅僅是唯一選拔人才的標準,在面試中求職者的情理表現,抗壓反應,協作溝通能力都被當作很重要的考核標準。所以在進入企業正式工作前,許多求職者不得不好好準備一番,順利通過面試,才有機會追逐心中的夢想!
  • 面試官問:「你的興趣愛好是什麼?」這樣回答,面試官最滿意
    1 面試官為什麼要問這道題?面試官時間是很寶貴的,在短短一個小時之內,需要對候選人深入的了解,所以問任何問題都是有目的性的,那面試官問:「你的興趣愛好是什麼?」有什麼目的呢?面試官問你有什麼體育運動方面的愛好,他可不是想尋找球友,而是想從側面了解你的工作精力和活力以及身體健康程度。面試官問你是否喜歡看書,他可能是想從側面看看你是否愛好學習。同時, 面試官也想了解候選人是否有不良嗜好,如果這個不良嗜好對於崗位有影響,可能就不不能要。
  • 面試官:如何用3個字描述雞和鳥的區別?小夥機智回答,被錄取
    面試官:如何用3個字描述雞和鳥的區別?90的小楊,前兩天終於再一次收到了自己曾先後3次面試,都被淘汰的一家銷售公司的面試通知,心裡興奮不已,有了前面的失敗經歷,這次他做足了準備,希望能順利通過。來到面試公司之後,小楊看到了,大約有二三十個"競爭對手",大家都在緊張的看著手裡的資料。
  • 面試官想知道什麼?不是有簡歷嗎?
    大家意見比較統一的吐槽面試官太「作」了。覺得視頻裡的面試人員好棒棒,好厲害!大家不要忘記,這只是一個視頻,是有腳本的。看看就好,切莫當真。(一)面試的本質面試的本質,是自我呈現。3、面試官一般在這時候會再低頭瀏覽一下簡歷,一邊聽候選人的講述,快速在形成對候選人關注點的問題。總上所述,所以做一個自我介紹是所有面試官最普遍的面試方式。
  • 女面試官:形象描述一下周歲和虛歲的區別?小夥的回答實在是高!
    女面試官:形象描述一下周歲和虛歲的區別?小夥的回答實在是高!對於每個人來說,都有過或多或少的面試經歷,但如果你的求職方向不一樣的話,面試官的考察側重點也會不一樣。做銷售的,面試官會側重你的溝通反應能力;做設計的,面試官側重於你的思維創意能力。做技術的,會側重於你的技術功底和沉澱。而銷售這個行業往往看起來入行門檻不高,卻對人挑戰不小,我的朋友王小二上周就經歷了一次有趣的面試。小王是在智聯招聘上看到這個招聘信息的,對方是一個軟體公司,主要是招聘軟體銷售。想想軟體公司,待遇都不是問題。