BAT常見的Java面試題系列之集合類
一、ArrayList和Vector的區別?如何理解Vector的執行緒安全?
共同點
1、兩者都實現了List介面,並且屬於有序儲存集合,可根據索引直接取值。
2、允許集合內的元素重複或為Null。
區別
1、ArrayList是執行緒不安全的,Vector是執行緒安全的(Vector類中所有自帶方法都是執行緒安全的,因為方法都使用synchronized關鍵字標註)
2、Vector擴容後會增長原來的一倍,ArrayList增長原來的0.5倍。
此外,關於Vector的執行緒安全問題,很多人都稱之為“有條件的執行緒安全”,它對於單獨的操作可以是執行緒安全的,但是某些操作序列可能需要外部同步。舉個例子,假如我們定義如下刪除Vector中最後一個元素方法,
public Object deleteLast(Vector v){
int lastIndex = v.size()-1;
v.remove(lastIndex);
}
這是一個複合操作,其中包括size()和remove(),一旦多執行緒進行訪問,有可能會丟擲資料越界的異常。當然了,我們同樣可以用 synchronized來修飾這個方法,但是synchronized 的開銷是很大的。為了保證執行緒的安全以及資源的合理利用,可以採用如下方法初始化ArrayList。
List<Map<String,Object>> data=Collections.synchronizedList(new ArrayList<Map<String,Object>>());
二、簡述ArrayList與LinkedList的使用場景以及原因?
執行緒安全性方面,ArrayList與LinkedList都是執行緒不安全的。
1、資料查詢檢索方面
ArrayList它支援索引直接訪問的方式(隨機訪問),而LinkedList則需要遍歷整個連結串列來獲取對應的元素。因此一般來說ArrayList的訪問速度是要比LinkedList要快的。這是由兩者底層的儲存結構決定的,ArrayList的底層是陣列,LinkedList的底層是雙向連結串列。
2、資料修改方面
ArrayList由於是陣列,對於刪除和修改而言消耗是比較大(複製和移動陣列實現),偶爾可能會導致對陣列重新進行分配;LinkedList是雙向連結串列刪除和修改只需要修改對應的指標即可,消耗是很小的
但是,ArrayList的增刪未必就是比LinkedList要慢。
若增刪都是在末尾來操作【每次呼叫的都是remove()和add()】,此時ArrayList就不需要移動和複製陣列來進行操作了。如果資料量有百萬級的時,速度是會比LinkedList要快的。
若刪除操作的位置是在中間。由於LinkedList的消耗主要是在遍歷上,ArrayList的消耗主要是在移動和複製上(底層呼叫的是arraycopy()方法,是native方法),所以ArrayList會相對快一些。
三、HashMap和Hashtable的區別?
共同點:
都是實現Map介面,並且都採用鍵值對的形式存取
區別:
1、同步性(重點)
HashMap執行緒不安全的,而Hashtable是執行緒安全的。在多執行緒併發場景下,往往不用Hashtable,而使用ConcurrentHashMap的鎖分段技術,它的實現原理是首先將資料分成一段一段的儲存,然後給每一段資料配一把鎖,當一個執行緒佔用鎖訪問其中一個段資料的時候,其他段的資料也能被其他執行緒訪問。使用方式如下:
Map<String,String> myMap = new ConcurrentHashMap<String,String>();
2、是否允許為null
HashMap物件的key、value值均可為null;而HahTable物件的key、value值均不可為null。且兩者的key值均不能重複,若新增key相同的鍵值對,後面的value會自動覆蓋前面的value,但不會報錯。
3、contains方法
Hashtable有contains方法,HashMap把Hashtable的contains方法去掉了,改成了containsValue和containsKey
4、繼承不同:
HashMap<K,V> extends AbstractMap<K,V>
public class Hashtable<K,V> extends Dictionary<K,V>
想要更加深入瞭解HashMap以及執行緒安全的同學,可以拜讀一下這幾篇文章:
四、Set裡的元素是不能重複的,那麼新增元素時,用什麼方法來去重? 是用==還是equals()?
HashSet中元素的儲存原理(雜湊演算法)
當向Set中新增物件時,首先呼叫此物件所在類的hashCode()方法,計算次物件的雜湊值,此雜湊值決定了此物件在Set中存放的位置;若此位置沒有被儲存物件則直接儲存,若已有物件則通過物件所在類的equals()比較兩個物件是否相同,相同則不能被新增。
下面通過解讀HashSet中add的原始碼,更加直觀的看到演算法過程:
//新增元素
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//map中put方法的原始碼
public V put(K key, V value) {
if (key == )
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != ; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key |||| key.equals(k)) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return ;
}
從原始碼中可以看出,HashSet的add方法實際上實現的是map物件的put方法,在put方法中不僅用到了==方法,還有equals方法,用==比較的是hashcode的值,而用equals比較的則是物件的具體內容,所以兩個方法都用到了。
五、Java中HashMap的key值要是為類物件則該類需要滿足什麼條件?
需要同時重寫該類的hashCode()方法和它的equals()方法。
- 從原始碼可以得知,在插入元素的時候是先算出該物件的hashCode。如果hashcode相等話的。那麼表明該物件是儲存在同一個位置上的。
- 如果呼叫equals()方法,兩個key相同,則替換元素
- 如果呼叫equals()方法,兩個key不相同,則說明該hashCode僅僅是碰巧相同,此時是雜湊衝突,將新增的元素放在桶子上
一般來說,我們會認為:只要兩個物件的成員變數的值是相等的,那麼我們就認為這兩個物件是相等的!因為,Object底層比較的是兩個物件的地址,而對我們開發來說這樣的意義並不大~這也就為什麼我們要重寫equals()方法
重寫了equals()方法,就要重寫hashCode()的方法。因為equals()認定了這兩個物件相同,而同一個物件呼叫hashCode()方法時,是應該返回相同的值的!
六、Collection、Collections以及CollectionUtils的區別?以及CollectionUtils的常用方法?
1、Collection是集合的上級介面,繼承它的有Set和List介面
2、Collections是集合的工具類,提供了一系列的靜態方法對集合的搜尋、查詢、同步等操作
3、而CollectionUtils是apache common包下的一個工具類,對於集合也有一系列的操作
CollectionUtils的常用方法列舉:
//1、判斷集合是否為空,如果為空返回false
CollectionUtils.isEmpty(null)
CollectionUtils.isEmpty(new ArrayList())
CollectionUtils.isEmpty({a,b})
//2、判斷集合是否不為空,如果不為空返回true
CollectionUtils.isNotEmpty(null)
CollectionUtils.isNotEmpty(new ArrayList())
CollectionUtils.isNotEmpty({a,b})
//2個集合間的操作:
//集合a: {1,2,3,3,4,5}
//集合b: {3,4,4,5,6,7}
//(並集): {1,2,3,3,4,4,5,6,7}
CollectionUtils.union(a, b)
//(交集):{3,4,5}
CollectionUtils.intersection(a, b)
//(交集的補集):{1,2,3,4,6,7}
CollectionUtils.disjunction(a, b)
//(交集的補集): {1,2,3,4,6,7}
CollectionUtils.disjunction(b, a)
//(A與B的差):{1,2,3}
CollectionUtils.subtract(a, b)
// (B與A的差): {4,6,7}
CollectionUtils.subtract(b, a)
七、Enumeration和Iterator介面的區別
大概的意思是:Iterator替代了Enumeration,Enumeration是一箇舊的迭代器了。與Enumeration相比,Iterator更加安全,因為當一個集合正在被遍歷的時候,它會阻止其它執行緒去修改集合。
- 我們在做練習的時候,迭代時會不會經常出錯,丟擲ConcurrentModificationException異常,說我們在遍歷的時候還在修改元素。
區別有三點:
- Iterator的方法名比Enumeration更科學
- Iterator有fail-fast機制,比Enumeration更安全
- Iterator能夠刪除元素,Enumeration並不能刪除元素
八、Iterator與ListIterator是什麼關係,兩者的優缺點有什麼?
兩者關係:
ListIterator繼承了Iterator介面,它用於遍歷List集合的元素。
區別
1、ListIterator有add()方法,可以向List中新增物件,而Iterator不能。
2、ListIterator和Iterator都有hasNext()和next()方法,可以實現順序向後遍歷。但是ListIterator有hasPrevious()和previous()方法,可以實現逆向(順序向前)遍歷。Iterator就不可以。也就是說Iterator是單向的,而ListIterator是雙向的。
3、ListIterator可以定位當前的索引位置,nextIndex()和previousIndex()可以實現,Iterator 沒有此功能。
4、都可實現刪除物件,但是ListIterator可以實現物件的修改,set()方法可以實現。Iterator僅能遍歷,不能修改。
九、ArrayList集合加入1萬條資料,應該怎麼提高效率
ArrayList的預設初始容量為10,要插入大量資料的時候需要不斷擴容,而擴容是非常影響效能的。因此,現在明確了10萬條資料了,我們可以直接在初始化的時候就設定ArrayList的容量!
這樣就可以提高效率了~