1. 程式人生 > >BAT常見的Java面試題系列之集合類

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是雙向連結串列刪除和修改只需要修改對應的指標即可,消耗是很小的

。因此一般來說LinkedList的增刪速度是要比ArrayList要快的。

但是,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的容量!

這樣就可以提高效率了~