Java集合容器面試題(面試必備)


萬惡之源:
在這裏插入圖片描述

1. 集合

1.1 什麼是集合

集合是存儲數據的容器,這裏的數據指的是對象,可以存儲不同的對象,並且長度可變。

1.2 集合特點

  • 集合用於對象封裝數據,存儲對象
  • 對象的個數可以確定時使用數組,不確定時使用集合,因爲集合長度可變

1.3 集合和數組得區別

  • 數組的長度是固定的,集合是可變的
  • 數組可以存儲基本數據類型,也可以存儲引用數據類型,集合只能存儲引用數據類型
  • 數組存儲的元素必須是同一個數據類型,集合存儲的對象可以是不同類型

1.3 什麼是集合框架

集合框架是爲了操作集合而規定的一種標準的體系結構,任何集合框架都包括三大塊內容:對外接口,接口實現,集合運算算法。
對外接口:即集合的抽象數據類型,接口提供了我們能夠對集合提供的內容進行操作。
接口實現:也就是集合框架的接口的具體實現,實際上是哪些可以複用的數據結構。
集合運算算法:例如,查找,排序算法,都是一些可以複用的函數。

1.4 集合框架的優點有哪些

  1. 降低核心類的開發成本,而非使用我們自己開發的集合類。
  2. 通過使用JDK附帶的集合類,可以降低開發成本。
  3. 集合框架類經過嚴格的測試,開發質量會有所提高。
  4. 容量自增長。

1.5 集合框架中的泛型有什麼優點?

自從JDK 5發行後,引入的泛型編程,在集合類和接口中都在大量使用泛型,泛型提供可以容納的對象類型,因此如果你添加了其它的對象類型,會出現類之間的轉換異常,編譯時就會報錯ClassCastException,有些錯誤就可以避免,這樣就會給運行時帶來好處。

1.6 常用的集合類

Map接口和Collection接口是所有集合類的父接口

  1. Collection:實現接口主要有Set,List,Queue
  2. Map:實現接口主要有Hashtable,HashMap,TreeMap,ConcurrentHashMap以及Properties
  3. Set接口主要實現類有Hashset,SortSet,LinkedHashSet
  4. List接口主要實現有ArrayList,LinkedList,Vector

1.5 哪些集合類是線程安全的?

  • Vector就比ArrayList多了個同步機制(線程安全)但因爲效率低,已經很少被使用了。
  • Stack:堆棧類,先進後出
  • HashTable:就比HashMap多了個線程安全
  • enumeration:枚舉,相當於迭代器

1.6 怎麼確保一個集合不被修改?

可以使用Colections工具類的方法,任何集合對象經過處理都不能被修改
否則就會拋出異常

        List<String> list = new ArrayList<>();
        list.add("x");
        Collection<String> collection = Collections.unmodifiableCollection(list);
        collection.add("y");
        System.out.println(collection.size());
Exception in thread "main" java.lang.UnsupportedOperationException

1.7 Java集合的快速失敗機制 “fail-fast”?

fail-fast是Java的一種錯誤檢測機制,當多線程對集合上的結構進行操作時,就可能產生fail-fast機制。
出現場景
假如存在兩個線程(線程1,線程2),線程1通過Iterator在遍歷集合A的元素,在某個線程2中修改了集合的結構(不是單純的修飾其內容),那麼這個時候程序就會拋出CurrentModificationException異常,從而產生fail-fast機制。
原因
迭代器在遍歷時直接訪問集合中的內容,並且在遍歷的時候使用一個modCount變量,集合在被遍歷的過程中,如果內容發生變化(如元素被刪除),則modCount就會發生改變。每次迭代器使用hashNext()/next()遍歷下一個元素之前,就會先比較mCount是否與expectedModCount相等,expectedModCount默認值等於集合元素的個數,如果是就返回遍歷,不是的話就拋出異常。

解決方法:

  • 在遍歷過程是,所有涉及改變modCount的值的地方全部加上synchronized
  • 使用CopyOnWriteArrayList(線程安全)來替換ArrayList
    在這裏插入圖片描述
    更多CopyOnWriteArrayList:

https://baijiahao.baidu.com/s?id=1666117483794506320&wfr=spider&for=pc

1.8 List如何一邊遍歷一邊刪除

先說說我們可能會犯的錯誤

   List<Integer> list = new ArrayList<Integer>();
   list.add(11);
   list.add(22);
   list.add(33);

 for (Integer integer : list) {
       list.remove(integer);
   }

如此我們會犯什麼錯了,看上去好像沒錯,但是運行之後就會產生上面1.8處說明的fail-fast機制,報出ConcurrentModificationException
產生的原因:和上面分析分析一致,foreach遍歷其實內部採用的就是Iterator的遍歷思想,當迭代器(線程1)在遍歷的時候會維護着一個modCount遍歷,但是如果list.remove(integer)(線程2)進行集合結構內容的改變時,就會改變modCount值,影響到迭代器遍歷,爆出線程併發異常。
解決方法:除了上面提到的兩個外這邊可以直接使用迭代器的刪除方法,如下:

   List<Integer> list = new ArrayList<Integer>();
   list.add(11);
   list.add(22);
   list.add(33);

   Iterator<Integer> it = list.iterator();
   while (it.hasNext()){
        Integer next = it.next();
        System.out.println(next);
       it.remove();
   }

注意:Iterator對象開始的cursor(遊標)是指向0,類似於頭指針,hasNext查看有沒有下一個元素,所以it.next();不能放remove下方。

2.Collection接口

2.1 List接口

2.1.1 Iterator是什麼

迭代器Iterator接口提供遍歷任何Collection接口, public interface Collection<E> extends Iterable<E>,我們可以從Collection中使用迭代器方法獲取迭代器實例,迭代器允許Java集合在遍歷的時候刪除元素。

2.1.2 Iterator有什麼特點,怎麼使用

   List<Integer> list = new ArrayList<Integer>();
   list.add(11);
   list.add(22);
   list.add(33);

   Iterator<Integer> it = list.iterator();
   while (it.hasNext()){
        Integer next = it.next();
        System.out.println(next);
   }

Iterator特點是單向遍歷,但是更加安全,它可以保證當前正在遍歷集合元素被修改的時候,爆出併發異常ConcurrentModificationException

2.1.3 如何一邊遍歷一邊刪除集合元素

### 1.8 List如何一邊遍歷一邊刪除

2.1.4 Iterator和ListIterator有什麼區別

  • Iterator可以遍歷List和Set的集合,而ListIterator只能遍歷L集合
  • Iterator只支持單向遍歷,而ListIterator支持雙向遍歷(前/後)
  • ListIterator實現Iterator接口,又添加了一些額外功能,如添加一個元素,替換一個元素。

2.1.5 遍歷List的方式

遍歷方法

  1. for循環遍歷:基於計數器,集合外部維護着一個計數器,然後依次讀取每個元素,直到讀到最後一個元素即可。
  2. Iterator遍歷:Iterator是面向對象的一種設計模式,目的是屏蔽不同集合的特點,統一遍歷集合接口,Collectioin中採用的迭代器模式。
  3. foreach:內部還是使用了迭代器的方式實現,使用時不需要顯示的聲明迭代器或計數器,優點時代碼簡潔,不易出錯,缺點是,在遍歷的時候不能操作集合,如增刪改,否則報會併發異常。

最佳方法:Java Collection接口中提供了RandomAccess 接口,用於標記集合是否支持RandomAccess接口

  1. 如果一個集合實現了該接口,則它支持快速隨機訪問,按位置讀取元素的平均時間複雜度是O(1),如ArrayList
  2. 如果集合沒有實現該接口,表示不支持RandomAccess接口,如LinkedList
    綜上:推薦的做法是,如果集合支持RandomAccess可以使用for遍歷,否則使用Iterator/foreach遍歷。

2.1.6 說下ArrayList的優缺點

優點

  • ArrayList底層採用數組實現,是一種隨機的訪問模式,並且實現了RandomAccess接口,因此查找的速度非常快。
  • ArrayList順序添加元素非常方便

缺點

  • 刪除元素的時候需要做元素的複製操作,如果元素的很多的話,很消耗性能。
  • 插入元素的時候,同樣遇到上面的問題,移動移動元素,讓出插入位置。

2.1.7 數組與List之間的轉換

  • 數組轉List:使用Arrays.asList方法
  • List轉數組:使用toArray方法
 List<Integer> list = new ArrayList<Integer>();
 list.add(11);
 list.add(22);
 list.add(33);

 Object[] objects = list.toArray();  //list->[]

 String []strs = new String[]{"aa","bb"};
 List<String> strings = Arrays.asList(strs);   //[]->list

2.1.8 ArrayList與LinkedList之間的區別

數據結構:ArrayList底層採用的是數組實現,LinkedList採用雙向鏈表實現。
數據訪問:ArrayList要優於LinkedList,因爲ArrayList底層採用數組實現,LinkedList採用線性的鏈式存儲結構,需要移動指針來訪問。
增刪效率:LinkedList要優於ArrayList,ArrayList增刪要複製元素,影響其它數組元素的下標,而LinkedList只需要移動指針。
線程安全:ArrayList和LinkedList都是線程不同步的,都不能保證線程安全。

綜上:如果對集合元素增刪比較頻繁的話推薦使用LinkedList,查找比較頻繁的話推薦使用ArrayList。

2.1.9 ArrayList與Vector之間的區別

ArrayList與Vector都是List接口的實現,List接口又實現了Collection接口,因此ArrayList與Vector都是有序的集合。

  • Vector相比ArrayList更爲安全,因此它增加了Synchronized來實現線程同步,因此是線程安全的,而ArrayList線程不安全。
  • ArrayList的性能要優於Vector

綜上:追求效率至上不需要保證線程安全的情況下推薦使用ArrayList,需要線程安全的情況下推薦Vector

2.1.10 插入數據ArrayList,LinkedList與Vector的性能

ArrayList和Vector底層都是採用數組實現,Vector相比ArrayList加入了線程安全機制,相比ArrayList更爲安全,但是效率卻不及ArrayList,它們都不適合用頻繁增刪集合元素,因此會影響到集合內的其它元素。
LinkedList底層採用雙向鏈表實現,增刪元素靠指針來完成,不會影響到集合內的其它元素,因此效率更高。

2.1.11 多線程場景下使用ArrayList

由於ArrayList非線程安全,多線程的場景下使用ArrayList,可能會出現問題,可以使用Collections的synchronizedList方法將其轉成一個線程安全的List再使用

  List<Integer> list = new ArrayList<Integer>();
  list.add(11);
  list.add(22);
  list.add(33);

  List<Integer> integers = Collections.synchronizedList(list);
  integers.add(44);

2.1.12 爲什麼ArrayList的elementData加上transient修飾

transient Object[] elementData; // non-private to simplify nested class access

回答問題前必須先明確transient的作用是什麼?
持久化對象是,如果一個對象不想用序列化的機制來保存它,就可以在前面加一個transient。
回到剛剛的問題elementData前面加上transient說明elementData不會被序列化。這樣做又什麼好處呢?請看ArrayList中兩個重要的函數

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 behavioural 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();
    }
}

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;
 
    // Read in size, and any hidden stuff
    s.defaultReadObject();
 
    // Read in capacity
    s.readInt(); // ignored
 
    if (size > 0) {
        // be like clone(), allocate array based upon size not capacity
        ensureCapacityInternal(size);
 
        Object[] a = elementData;
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
}

elementData是ArrayList存儲元素的數組,是一個緩存數組,它通常會預留一些空間,等容量不足再進行擴容。每次序列化時,先調用defaultWriteObject方法來序列化ArrayList中哪些非transient修飾的內容,然後遍歷elementData,用readObject和writeObject方法來實序列化就可以保證之序列化哪些實際存儲的元素,從不是整個數組,從而節省了序列化過程中的空間和時間。

參考:https://blog.csdn.net/weixin_33924220/article/details/92348708

2.1.13 List與Set的區別

List和Set都繼承於Collection接口
特點
List:一個有序的(存入和取出的順序一致)的容器,可以插入null值,可以又重複元素,元素都有索引,常用的實現類有ArrayList,LinkedList,Vector.
Set:一個無序的(存入和取出的順序可能不一致)的容器,不允許重複元素,只允許存入一個null,必須保證元素的唯一性,Set常見的實現類有HashSet,LinkedHashSet,TreeSet.
遍歷
List因爲是有序的,所以支持使用for,也就是通過下標來遍歷,迭代器遍歷,而Set只支持迭代器遍歷,因爲它是無序的,無法通過下標來獲取值。

2.2 Set接口

2.2.1 說一下HashSet的實現原理

HashSet是基於HashMap實現的,HashSet的值存放在HashMap的key上,相關HashSet的操作,基本上都是直接調用底層的HashMap的相關方法來實現的,並且HashSet不可重複,是無序的。

2.2.2 說一下HashSet如何檢查重複,如何保證數據不可重複?

說在前面:HashSet是Set的實現,不是Map接口下的實現。。。。
HashSet的add方法底層使用HashMap的put方法來插入數據,判斷數據是否存在的依據,不僅要比較hashcode值還要equals方法。
HashSet部分源碼:

private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;

public HashSet() {
    map = new HashMap<>();
}

public boolean add(E e) {
    // 調用HashMap的put方法,PRESENT是一個至始至終都相同的虛值
	return map.put(e, PRESENT)==null;
}

HashMap的key是唯一的,由源碼可知HashSet的key就是使用HashMap的key,如果HashSet中遇到相同的key時,對於新的value就會覆蓋舊的value,返回舊的value,所以是不會重複的。
HashMap比較key是否相等是先比較hashcode,再equals。

回顧hashcode和equals。
Java對hashcode()和equals()有以下規定:

  1. 如果兩個對象相等,則它們的hashcode()一定相等
  2. 如果兩個對象相等,則它們的equals也返回true
  3. 如果兩個對象的hashcode相等,但是它們不一定相等。

綜上:我們再重寫equals時,規定也必須重寫hashcode
hashcode()的默認行爲是對堆上的對象產生獨特值,如果沒有重寫hashcode(),無論如何兩個對象都不會相等(即使兩個對象指向相同的數據)
== 與 equals的區別

  • == 是判斷兩個變量或實例是否指向相同的內存地址,equals是判斷兩個變量或實例指向的內存地址對應的值是否相等
  • ==是對內存地址進行比較,equals是對字符串進行比較
  • ==是比較引用是否相同,equals是值是否相同

2.2.3 HashSet和HashMap的區別?

HashMap HashSet
實現Map接口 實現Set接口
存儲鍵值對 存儲對象
調用put方法向map中添加元素 調用add方法向Set中添加元素
HashMap使用key來計算hashcode值 HashSet使用成員對象來計算hashcode值,如果相等則在調用equals判斷,如果對象不同的話,返回false
HashMap相比HashSet快,因爲它是使用唯一的key來獲取值 HashSet比HashMap稍慢點

2.3 Queue的poll()和remove()區別是什麼

  Queue<String> queue = new LinkedList<>();
        queue.offer("11");
//        System.out.println(queue.poll()); //11
//        System.out.println(queue.poll());  //null
        System.out.println(queue.remove()); //11
        System.out.println(queue.remove()); //java.util.NoSuchElementException

區別已經很明顯了

  • 相同點:poll()和remove()都是刪除隊列元素並返回刪除元素
  • 不同點:當隊列中沒有元素時remove會拋出異常java.util.NoSuchElementException,而poll返回null

3. Map接口

https://blog.csdn.net/JAYU_37/article/details/104433085

4. 輔助工具類

4.1 comparable和comparator的區別

  1. comparable在java.lang.Comparable它有一個comparaTo方法用於排序;comparator在java.util包中,它有一個compare(Object obj1,Object obj2)用於排序
  2. 如果實現類沒有實現comparable接口或者實現了comparable接口但是不滿足於comparable接口提供的compareTo方法,我們可以自定義比較方法,只需要實現Comparator接口重寫compare(Object obj1,Object obj2)方法。
  3. comparable需要比較對象來實現接口,使用對象調用方法來比較對象,需要改變對象內部結構(重寫compareTo),耦合度高。comparator相當於一個通用的比較工具接口,需要定製一個比較類去實現它,重寫裏面的compare方法,方法參數即比較的對象,對象不用做任何的改變,解耦。

4.2 Collection和Collections的區別

Collectionjava.utils.Collectioni的集合接口,也是集合的頂級接口,它提供了對集合對象的基本操作和通用方法,Collection類在Java類庫中有很多體現,直接實現類有List,Set。
Collections:是集合類的一個工具類,提供了一系列的靜態方法,用於對集合中的元素進行排序,搜索等操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章