本期是【對線面試官】系列文章的第2期,主要是Java List高頻面試題目。
回覆【面試】獲取大彬精心整理的大廠面試手冊,已助力多位小夥伴斬獲大廠offer!
面試現場
面試官:你好,我是面試官xxx,請問你是大彬嗎?
大彬:面試官,您好,我是大彬
面試官:現在方便麵試嗎?
大彬:嗯嗯,可以的
面試官:那我們現在開始面試吧
面試官:看你簡歷上寫了熟悉集合相關內容,你瞭解Java的List嗎?
大彬:嗯,List是一個介面,常見的實現類有ArrayList和LinkedList
面試官:講講這兩個實現類的區別?
獨白:老八股文了哈哈
大彬:ArrayList的底層資料結構是陣列,支援下標訪問,查詢資料快。預設初始值大小為10,容量不足時會進行擴容
大彬:而LinkedList的底層資料結構是連結串列,將元素新增到連結串列的末尾,無需擴容
面試官:嗯嗯,剛剛你提到ArrayList的擴容,詳細講講?
獨白:好傢伙,就知道你會問這個,八股文都已經準備好了。
大彬:ArrayList擴容原始碼實現如下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); //擴容
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
大彬:可以看到,在grow方法裡面進行擴容,將陣列容量擴大為原來的1.5倍。
大彬:舉個例子,如果初始化的值是8,當新增第9個元素的時候,發現陣列空間不夠,就會進行擴容,擴容之後容量為12。
大彬:擴容之後,會呼叫Arrays.copyOf()方法對陣列進行拷貝。
面試官:嗯,不錯,那ArrayList和LinkedList分別適用於什麼場景?
大彬:對於隨機index訪問的get和set方法,ArrayList的速度要優於LinkedList。因為ArrayList直接通過陣列下標直接找到元素;LinkedList要移動指標遍歷每個元素直到找到為止。
大彬:新增和刪除元素,LinkedList的速度要優於ArrayList。因為ArrayList在新增和刪除元素時,可能擴容和複製陣列;而LinkedList的新增和刪除操作只需要修改指標即可。
大彬:因此,ArrayList適用於查詢多,增刪少的場景。而LinkedList適用於查詢少,增刪多的場景
面試官:講講Set和List的區別?
大彬:List 以索引來存取元素,有序的,元素是允許重複的,可以插入多個null;Set 不能存放重複元素,無序的,只允許插入一個null。
大彬:List 底層實現有陣列、連結串列兩種方式;Set 基於 Map 實現,Set 裡的元素值就是 Map的鍵值。
面試官:瞭解Vector嗎?
大彬:嗯,Vector是底層結構是陣列,現在基本沒有使用Vector了,因為操作Vector效率比較低。相對於ArrayList,它是執行緒安全的,在擴容的時候容量擴充套件為原來的2倍。
面試官:嗯,那你還知道有哪些執行緒安全的List嗎?
大彬:可以使用Collections.synchronizedList()方法返回一個執行緒安全的List。
大彬:還有另一種方式,使用CopyOnWriteArrayList。
面試官:嗯哼,剛剛你提到CopyOnWriteArrayList,詳細講講它的原理?
獨白:這也太捲了吧,一言不合就是底層原理...
大彬:CopyOnWriteArrayList是一個執行緒安全的List,底層是通過複製陣列的方式來實現的。
大彬:所謂的CopyOnWrite,就是寫時複製。
大彬:當我們往容器新增元素時,不直接往容器新增,而是先將當前容器進行復制,複製出一個新的容器,然後往新的容器新增元素,新增完元素之後,再將原容器的引用指向新容器。
大彬:這樣做的好處就是可以對CopyOnWrite容器進行併發的讀而不需要加鎖,因為當前容器不會被修改。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock(); //add方法需要加鎖
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1); //複製新陣列
newElements[len] = e;
setArray(newElements); //原容器的引用指向新容器
return true;
} finally {
lock.unlock();
}
}
面試官:那你能說說CopyOnWriteArrayList有什麼缺點嗎?
大彬:主要有以下兩個問題。
大彬:記憶體佔用問題。由於CopyOnWrite的寫時複製機制,在進行寫操作的時候,記憶體裡會同時駐紮兩個物件的記憶體。
大彬:CopyOnWrite容器不能保證資料的實時一致性,可能讀取到舊資料。
面試官:嗯,可以。
面試官:問一些平時開發經常遇到的問題,怎麼給List排序呢?
大彬:可以使用list自身的sort方法,或者使用Collections.sort(list)方法。
面試官:怎麼在遍歷 ArrayList 時移除一個元素?
大彬:如果使用foreach刪除元素的話,會導致快速失敗(fast-fail)問題,可以使用迭代器的 remove() 方法,避免fast-fail問題。
Iterator itr = list.iterator();
while(itr.hasNext()) {
if(itr.next().equals("dabin") {
itr.remove();
}
}
面試官:基礎還不錯!回去等通知
大彬:好的,謝謝你
獨白:回去等通知?不會涼了吧...