java開發面試題-容器篇(二)


以下的題目中,有些是我網上查找的題目,有的是增加了一些擴展內容幫助理解,題目不能硬背,要充分的理解。

1. java容器都有哪些?

  • 數組
    數組長度限制爲 Integer.Integer.MAX_VALUE

(int的取值範圍爲(-2147483648~2147483647),佔用4個字節(-2的31次方到2的31次方-1) )

  • String
    String的長度限制: 底層是char 數組 長度 Integer.MAX_VALUE 線程安全的.
  • java.util下的集合容器

在這裏插入圖片描述

  1. Set下各種實現類對比

HashSet基於哈希表實現,有以下特點:

      1.不允許重複
       2.允許值爲null,但是隻能有一個
       3.無序的。
       4.沒有索引,所以不包含索引操作的方法

LinkedHashSet跟HashSet一樣都是基於哈希表實現。只不過linkedHashSet在hashSet的基礎上多了一個鏈表,這個鏈表就是用來維護容器中每個元素的順序的。

TreeSet是SortedSet接口的唯一實現類,是基於二叉樹實現的。TreeSet可以確保集合元素處於排序狀態。TreeSet支持兩種排序方式,自然排序 和定製排序,其中自然排序爲默認的排序方式。向TreeSet中加入的應該是同一個類的對象。

  1. List下各種實現類對比。(這幾個類都是有序的,允許重複的)

ArrayList是基於數組實現的,其特點是查詢快,增刪慢。
增刪慢是因爲數組是不能擴容的,一旦增加或者刪除元素,內部操作就是新開闢一個數組把元素copy到新的數組,老的數 組等待被垃圾回收。

內部實現的源碼:
在這裏插入圖片描述 LinkedList是基於鏈表實現的。相比於ArrayList其特點是查詢慢,增刪快。

查詢慢:因爲鏈表在內存中開闢的空間不一定是連續的(基本上不可能是連續的)所以鏈表實現的方式是每個元素節點都會存放自己的地址,數據以及下一個節點的地址,這樣把所有的元素連接起來。所以當要查詢元素時只能一個一個的往下找,相比於數組的首地址加下標會慢上不少。

Vector也是基於數組實現的,相比於arrayList它是線程安全的。如果不考慮線程安全它,ArrayList性能更優。

  1. Map是雙列集合的超類。也就是鍵值對形式。

HashMap和Hashtable都實現了Map接口,但決定用哪一個之前先要弄清楚它們之間的分別。主要的區別有:線程安全性,同步(synchronization),以及速度。

  • HashMap是非synchronized的(線程不安全)多個線程是不能共享HashMap的,如果你不需要同步,只需要單一線程,那麼使用HashMap性能要好過Hashtable。並可以接受null(HashMap可以接受爲null的鍵值(key)和值(value),而Hashtable則不行),HashMap無序。
  • LinkedHashMap和hashMap的區別在於多維護了一個鏈表,用來存儲每一個元素的順序,就跟HashSet和LinkedHashSet差不多。
  • 建議多使用HashMap,在需要排序的Map時候才用TreeMap。

2. Collection 和 Collections 有什麼區別?

  1. java.util.Collection 是一個集合接口(集合類的一個頂級接口)。它提供了對集合對象進行基本操作的通用接口方法。Collection 接口在 Java類庫中有很多具體的實現。Collection 接口的意義是爲各種具體的集合提供了最大化的統一操作方式,其直接繼承接口有 List 與 Set。
  2. Collections 則是集合類的一個**工具類/**幫助類,其中提供了一系列靜態方法,用於對集合中元素進行排序、搜索以及線程安全等各種操作。

3. List 、 Set 、 Map 之間的區別是什麼?

這個問題要從list、set 、map 本身來說,通常犯的錯誤就是直接說他們的子類,比如人家問的是list, 你直接就說ArrayList以及LinkList 等。 這樣回答不僅內容很多容易亂,而且也是答非所問,更沒有指出這些抽象類的本質區別。

  • list 和set 有共同的父類(collection) 它們的用法也是一樣的, 其根本的區別就是set中不能有相同的元素 list中可以有。
  • list和set的用途非常廣泛 list可以完全代替數組來使用。
  • map 是獨立的合集 它使用鍵值對的方式來儲存數據 鍵不能有重複的 值可以有
  • map不像上邊兩種集合那個用的廣泛 不過在servlet 和jsp中 map可是絕對的重中之重 頁面之間傳值全靠map 。

4. HashMap 和 Hashtable 有什麼區別?

  1. 繼承的父類不同
    Hashtable繼承自Dictionary類,而HashMap繼承自AbstractMap類。但二者都實現了Map接口。

  2. 線程安全性不同
    Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情況下是非Synchronize的。在多線程併發的環境下,可以直接使用Hashtable,不需要自己爲它的方法實現同步,但使用HashMap時就必須要自己增加同步處理。
    Hashtable 線程安全很好理解,因爲它每個方法中都加入了Synchronize。
    HashMap底層是一個Entry數組,當發生hash衝突的時候,hashmap是採用鏈表的方式來解決的,在對應的數組位置存放鏈表的頭結點。對鏈表而言,新加入的節點會從頭結點加入。多線程獲取這個頭節點如果相同,在執行操作時就會有數據覆蓋丟失等情況。

  3. 是否提供contains方法
    HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因爲contains方法容易讓人引起誤解。
    Hashtable則保留了contains,containsValue和containsKey三個方法,其中contains和containsValue功能相同。

  4. key和value是否允許null值
    Hashtable中,key和value都不允許出現null值。但是如果在Hashtable中有類似put(null,null)的操作,編譯同樣可以通過,因爲key和value都是Object類型,但運行時會拋出NullPointerException異常,這是JDK的規範規定的。
    HashMap中,null可以作爲鍵,這樣的鍵只有一個;可以有一個或多個鍵所對應的值爲null。當get()方法返回null值時,可能是 HashMap中沒有該鍵,也可能使該鍵所對應的值爲null。因此,在HashMap中不能由get()方法來判斷HashMap中是否存在某個鍵, 而應該用containsKey()方法來判斷。

  5. 兩個遍歷方式的內部實現上不同
    Hashtable、HashMap都使用了 Iterator。而由於歷史原因,Hashtable還使用了Enumeration的方式 。

  6. hash值不同
    hashCode是jdk根據對象的地址或者字符串或者數字算出來的int類型的數值。
    Hashtable計算hash值,直接使用key的hashCode(),而HashMap重新計算了key的hash值。

  7. 內部實現使用的數組初始化和擴容方式不同
    HashTable在不指定容量的情況下的默認容量爲11,而HashMap爲16,
    Hashtable擴容時,將容量變爲原來的2倍加1,而HashMap擴容時,將容量變爲原來的2倍。

5. 如何決定使用 HashMap 還是 TreeMap?

對於在 Map 中插入、刪除和定位元素這類操作,HashMap 是最好的選擇。
然而,假如你需要對一個有序的 key 集合進行遍歷,TreeMap 是更好的選
擇。基於你的 collection 的大小,也許向 HashMap 中添加元素會更快,將
map 換爲 TreeMap 進行有序 key 的遍歷。

6. 說一下 HashMap 的實現原理?

HashMap 概述: HashMap 是基於哈希表的 Map 接口的非同步實現。此實現提供 所有可選的映射操作,並允許使用 null 值和 null 鍵。此類不保證映射的順序,特別是它不保證該順序恆久不變。
HashMap 的數據結構: 在 java 編程語言中,最基本的結構就是兩種,一個是 數組,另外一個是模擬指針(引用),所有的數據結構都可以用這兩個基本結 構來構造的,HashMap 也不例外。HashMap 實際上是一個“鏈表散列”的數據結構,即數組和鏈表的結合體。
在這裏插入圖片描述
核心的部分,Entry 是一個 static class,其中包含了 key 和 value,也就是鍵值對,另外還包含了一個 next 的 Entry 指針。我們可以總結出:Entry 就是數組中的元素,每個 Entry 其實就是一個 key-value 對,它持有一個指向下一個元素的引用,這就構成了鏈表。

7. 說一下 HashSet 的實現原理?

詳細參考: https://www.cnblogs.com/skillking/p/7250606.html

  • HashSet 底層由 HashMap 實現
  • HashSet 的值存放於 HashMap 的 key 上
  • HashMap 的 value 統一爲 PRESENT

部分源碼如下:

   /** 
     * 如果此set中尚未包含指定元素,則添加指定元素。 
     * 更確切地講,如果此 set 沒有包含滿足(e==null ? e2==null : e.equals(e2)) 
     * 的元素e2,則向此set 添加指定的元素e。 
     * 如果此set已包含該元素,則該調用不更改set並返回false。 
     * 
     * 底層實際將將該元素作爲key放入HashMap。 
     * 由於HashMap的put()方法添加key-value對時,當新放入HashMap的Entry中key 
     * 與集合中原有Entry的key相同(hashCode()返回值相等,通過equals比較也返回true), 
     * 新添加的Entry的value會將覆蓋原來Entry的value,但key不會有任何改變, 
     * 因此如果向HashSet中添加一個已經存在的元素時,新添加的集合元素將不會被放入HashMap中, 
     * 原來的元素也不會有任何改變,這也就滿足了Set中元素不重複的特性。 
     * @param e 將添加到此set中的元素。 
     * @return 如果此set尚未包含指定元素,則返回true。 
     */  
    public boolean add(E e) {  
    return map.put(e, PRESENT)==null;  
    } 

什麼是哈希表?
哈希表(Hash table,也叫散列表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。

8. ArrayList 和 LinkedList 的區別是什麼?

從底層的實現原理的不同進而導致了其特性的不同:

  • ArrayList是基於數組實現的,其特點是查詢快,增刪慢。
    增刪慢是因爲數組是不能擴容的,一旦增加或者刪除元素,內部操作就是新開闢一個數組把元素copy到新的數組,老的數 組等待被垃圾回收。
  • LinkedList是基於鏈表實現的。相比於ArrayList其特點是查詢慢,增刪快。
    查詢慢:因爲鏈表在內存中開闢的空間不一定是連續的(基本上不可能是連續的)所以鏈表實現的方式是每個元素節點都會存放自己的地址,數據以及下一個節點的地址,這樣把所有的元素連接起來。所以當要查詢元素時只能一個一個的往下找,相比於數組的首地址加下標會慢上不少。

綜上: 在實際應用中如果有頻繁的增刪操作(如循環中),還是建議使用LinkedList吧。

9. 如何實現數組和 List之間的轉換?

一種是通過集合本身的一些方法和藉助於Arrays工具類

  1. List轉換成爲數組:調用ArrayList的toArray方法。
  2. 數組轉換成爲 List:調用Arrays的asList方法
    示例代碼如下:
        List<String> list = Arrays.asList("1", "2", "3", "4");
        list.stream().forEach(System.out::println);
        // 傳統方式
        // 1. List 轉 array
        Object[] arrays = list.toArray();
        Stream<Object> arrays1 = Stream.of(arrays);
        arrays1.forEach(System.out::println);
        // 2. 數組轉爲 list
        List<Object> objects = Arrays.asList(arrays);

第二種就是利用jdk8中提供的新特性,Stream流

  1. List轉換成爲數組:調用Stream的toArray方法。
  2. 數組轉換成爲 List:調用Arrays的asList方法
        // 1. list轉換爲array   Stream的 toArray方法
        Object[] toArray = list.stream().toArray();
        System.out.println("===============================");
        Stream<Object> arrays2 = Stream.of(toArray);
        arrays2.forEach(System.out::println);
        // 2. array 轉爲 list  Stream的 collect方法
        Stream<Object> list3 = Stream.of(toArray);
        List<Object> collect = list3.collect(Collectors.toList());
        collect.forEach(System.out::println);

10 . ArrayList 和 Vector 的區別是什麼?

  • Vector是同步的,而ArrayList不是。然而,如果你尋求在迭代的時候
    對列表進行改變,你應該使用 CopyOnWriteArrayList。
  • ArrayList比Vector 快,它因爲有同步,不會過載。
  • ArrayList更加通用,因爲我們可以使用Collections工具類輕易地獲取
    同步列表和只讀列表。

11. Array 和 ArrayList 有何區別

  • Array可以容納基本類型和對象,而 ArrayList只能容納對象。
  • Array是指定大小的,而ArrayList大小是固定的。
  • Array沒有提供ArrayList那麼多功能,比如addAll、removeAll和iterator等。

1.ArrayList是Array的複雜版本;
2.存儲的數據類型:Array只能存儲相同數據類型的數據,而ArrayList可以存儲不同數據類型的數據;
3.長度的可變:Array的長度是固定的,而ArrayList的長度是可變的。

12. 在 Queue 中 poll() 和 remove() 有什麼區別

隊列是一種特殊的線性表,它只允許在表的前端進行刪除操作,而在表的後端進行插入操作。
LinkedList類實現了Queue接口,因此我們可以把LinkedList當成Queue來用。

  • offer,add 區別:

一些隊列有大小限制,因此如果想在一個滿的隊列中加入一個新項,多出的項就會被拒絕。

這時新的 offer 方法就可以起作用了。它不是對調用 add() 方法拋出一個 unchecked 異常,而只是得到由 offer() 返回的 false。

  • poll,remove 區別:

remove() 和 poll() 方法都是從隊列中刪除第一個元素。remove() 的行爲與 Collection 接口的版本相似, 但是新的 poll() 方法在用空集合調用時不是拋出異常,只是返回 null。因此新的方法更適合容易出現異常條件的情況。

  • peek,element區別:

element() 和 peek() 用於在隊列的頭部查詢元素。與 remove() 方法類似,在隊列爲空時, element() 拋出一個異常,而 peek() 返回 null。

也就是新增的方法 offer添加,poll 移除 ,peek 查詢時,若集合爲空則不再拋出異常,而是做了優化返回false,或者null。

    //add()和remove()方法在失敗的時候會拋出異常(不推薦)
        Queue<String> queue = new LinkedList<String>();
        //添加元素
        queue.offer("a");
        queue.offer("b");
        queue.offer("c");
        queue.offer("d");
        queue.offer("e");
System.out.println("poll="+queue.poll()); //返回第一個元素,並在隊列中刪除
System.out.println("element="+queue.element()); //返回第一個元素 
System.out.println("peek="+queue.peek()); //返回第一個元素

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

  • vector:就比 arraylist 多了個同步化機制(線程安全),因爲效率較低,現在已經不太建議使用。在 web 應用中,特別是前臺頁面,往往效率(頁面響應速度)是優先考慮的。
  • statck: 堆棧類,先進後出。

Java Stack 類
棧是Vector的一個子類,它實現了一個標準的後進先出的棧。
堆棧只定義了默認構造函數,用來創建一個空棧。 堆棧除了包括由Vector定義的所有方法,也定義了自己的一些方法。
Stack.Peek 與 stack.pop 的區別?
相同點:大家都返回棧頂的值。
不同點:peek 不改變棧的值(不刪除棧頂的值),pop會把棧頂的值刪除。

  • hashtable:就比 hashmap 多了個線程安全和key,value是否允許爲null 值。
  • enumeration:枚舉,相當於迭代器。

Java Enumeration接口
Enumeration接口中定義了一些方法,通過這些方法可以枚舉(一次獲得一個)對象集合中的元素。
這種傳統接口已被迭代器取代,雖然Enumeration 還未被遺棄,但在現代代碼中已經被很少使用了。儘管如此,它還是使用在諸如Vector和Properties這些傳統類所定義的方法中,除此之外,還用在一些API類,並且在應用程序中也廣泛被使用。

14. 迭代器 Iterator 是什麼?

爲了方便的處理集合中的元素,Java中出現了一個對象,該對象提供了一些方法專門處理集合中的元素.例如刪除和獲取集合中的元素.該對象就叫做迭代器(Iterator).
對 Collection 進行迭代的類,稱其爲迭代器。還是面向對象的思想,專業對象做專業的事情,迭代器就是專門取出集合元素的對象。不能直接創建對象(通過new),該對象是以內部類的形式存在於每個集合類的內部。
如何獲取迭代器?Collection接口中定義了獲取集合類迭代器的方法(iterator()),所以所有的Collection體系集合都可以獲取自身的迭代器。
1.Iterable
正是由於每一個容器都有取出元素的功能。這些功能定義都一樣,只不過實現的具體方式不同(因爲每一個容器的數據結構不一樣)所以對共性的取出功能進行了抽取,從而出現了Iterator接口。而每一個容器都在其內部對該接口進行了內部類的實現。也就是將取出方式的細節進行封裝。

15. Iterator 怎麼使用? 有什麼特點?

Java 中的 Iterator 功能比較簡單,並且只能單向移動:
(1) 使用方法 iterator()要求容器返回一個 Iterator。第一次調用
Iterator 的 next()方法時,它返回序列的第一個元素。注意:iterator()
方法是 java.lang.Iterable 接口,被 Collection 繼承。
(2) 使用 next()獲得序列中的下一個元素。
(3) 使用 hasNext()檢查序列中是否還有元素。
(4) 使用 remove()將迭代器新返回的元素刪除。
Iterator 是 Java 迭代器最簡單的實現,爲 List 設計的 ListIterator 具
有更多的功能,它可以從兩個方向遍歷 List,也可以從 List 中插入和刪
除元素。

	static List<String> list = new ArrayList<String>();
	static {
		list.add("111");
		list.add("222");
		list.add("333");
	}
	//使用 hasNext 和 next遍歷 
	public static void testIteratorNext() {
		Iterator<String> iterator = list.iterator();
		while (iterator.hasNext()) {
			String str = iterator.next();
			System.out.println(str);
		}
	}

在jdk8以後推薦使用 Stream流的方式。

16. Iterator 和 ListIterator 有什麼區別?

  • Iterator 可用來遍歷 Set 和 List 集合,但是 ListIterator 只能用來遍歷List。
  • Iterator 對集合只能是前向遍歷,ListIterator 既可以前向也可以後向。
  • ListIterator 實現了 Iterator 接口,幷包含其他的功能,比如:增加元素,替換元素,獲取前一個和後一個元素的索引,等等。

17. 怎麼確保一個集合不能被修改?

  1. 可以使用 Collections. unmodifiableCollection(Collection c) 方法來創建一個只讀集合,這樣改變集合的任何操作都會拋出 Java.lang.UnsupportedOperationException 異常。
List<String> list = new ArrayList<>();
list.add("A");
Collection<String> unmlist = Collections. unmodifiableCollection(list);
unmlist.add("B"); // 運行時此行報錯
System.out.println(list.size());
  1. 可以使用Arrays.asList() 方法創建一個集合,該集合也是不可以修改的。
List<Integer> list= Arrays.asList(11, 22, 33, 44);

如果修改改集合同樣會報出 Java.lang.UnsupportedOperationException 異常。

關於此題的更詳細解讀 請移步 這裏

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