2020年的面試整理,請指教,關於(1)集合的整理

好記性不如爛筆頭,面對面試,該複習還是得複習,要不然被摁在地上摩擦,摩擦,在摩擦.......

直接開門見山,希望趕緊面完,還得回家看我家的小可愛,麼麼噠

一、關於集合的整理

在下就不一一整理了,主要整理下面試中常問的:

(1)ArrayList

底層是個動態數組,每次創建一個ArrayList實例時會分配一個初始化容量,以add方法爲例,如果沒有指定容量,當執行add方法時,會先判斷當前數組是不是爲空,如果爲空則給保存數組的對象分配一個初始化容量,默認爲10,當添加大容量時,會先增加數組的大小,也就是我們常說的擴容,默認擴容一半,調用grow(),Arrays.copyof()等方法進行處理,他是線程不安全,允許元素爲null當然,數組是有下標/索引的,查詢時可以根據下標/索引提升查詢效率,插入或者刪除時會導致後面的元素重新調整索引位置,產生一定的性能消耗。

(2)LinkedList

底層是個雙向鏈表的數據結構,添加刪除都是靠移動指針來完成的,所以性能較ArrayList要高出好多,他是一個雙向鏈表,沒有初始化大小,也沒有擴容,但是在查詢的時候,需要根據二分法來看index和size的距離來判斷是從頭結點正序查還是從尾節點倒序查,從源代碼中可以看出,判斷給定的索引值,若索引值大於整個鏈表長度的一半,則從後往前遍歷找,若索引值小於整個鏈表的長度的一半,則從前往後遍歷找。這樣就可以保證,不管鏈表長度有多大,搜索的時候最多隻搜索鏈表長度的一半就可以找到,相對來說大大提升了效率。總結來說,LinkedList 插入,刪除都是移動指針效率很高。查找需要進行遍歷查詢,效率較低。(3)ArrayList和linkedlist的區別

  • 1. 是否保證線程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保證線程安全;

  • 2. 底層數據結構: Arraylist 底層使用的是Object數組;LinkedList 底層使用的是雙向鏈表數據結構

  • 3. 插入和刪除是否受元素位置的影響: ① ArrayList 採用數組存儲,所以插入和刪除元素的時間複雜度受元素位置的影響。 比如:執行add(E e)方法的時候, ArrayList 會默認在將指定的元素追加到此列表的末尾,這種情況時間複雜度就是O(1)。但是如果要在指定位置 i 插入和刪除元素的話(add(int index, E element))時間複雜度就爲 O(n-i)。因爲在進行上述操作的時候集合中第 i 和第 i 個元素之後的(n-i)個元素都要執行向後位/向前移一位的操作。 ② LinkedList 採用鏈表存儲,所以插入,刪除元素時間複雜度不受元素位置的影響,都是近似 O(1)而數組爲近似 O(n)。

  • 4. 是否支持快速隨機訪問: LinkedList 不支持高效的隨機元素訪問,而 ArrayList 支持。快速隨機訪問就是通過元素的序號快速獲取元素對象(對應於get(int index)方法)。

  • 5. 內存空間佔用: ArrayList的空 間浪費主要體現在在list列表的結尾會預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗比ArrayList更多的空間(因爲要存放直接後繼和直接前驅以及數據)

(4)hashmap底層原理

我們主要是通過put()和get()方法存儲和獲取對象,當鍵值傳遞給put時,他會調用鍵對象的hashcode()方法計算出hashcode值,然後找到bucket位置來存儲值對象,當獲取時,會通過鍵對象的equals方法找到正確的鍵值對,然後返回對象,他的底層數據結構是數組+鏈表+紅黑樹,紅黑樹就是一個查找樹,可以提供查詢效率,數組則是hashmap 的主體,也就是我們常說的entry數組,該數組是hashmap的基本組成單元,每個數組存儲一個key-value鍵值對,鏈表則是爲了解決hash衝突,所謂hash衝突就是當我們對某個元素進行hash運算時,得到一個存儲地址,然後在存值過程中發現該位置已被其他元素佔用,這就是哈希衝突或者哈希碰撞,當發生哈希衝突並且實際存儲鍵值對的size大於閾值的時候,需要進行擴容resize操作,擴容時,需要創建一個新的數組,然後將之前的entry數組全部遷移過去,擴容原來的2倍,擴容是個拷貝的過程,相對來說比較消耗資源,如果定位到數組不含列表,也就是entry指向null,那麼對於添加查找等只需要一次尋址即可,如果定位到數組包含鏈表,對於添加操作,則需要遍歷鏈表,存在就覆蓋,否則新增,對於查找來說,仍需遍歷鏈表,然後通過key對象的equals方法逐一對比查找,所以,處於性能考慮,hashmap中出現鏈表越少越好。hashmap允許鍵值爲空,不會拋出空指針異常,將元素添加到table[0]的位置,因爲hashmap不支持線程同步,費線程安全,如果一個時刻有多個線程同時寫hashmap,可能會導致線程不安全,如需同步,可以使用collections和SynchronizedMap使hashmap具有同步能力,或者也可使用concurrentHashMap。

(5)hashtable

可以等同hashmap的實現,他是通過synchronized同步get,put等方法,並且key不允許爲null保證線程安全。

(6)hashset

無序,不重複,底層使用hashmap的實現不重複,使用map鍵存儲保證key不重複,value只是一個虛擬值。

(6)treemap

可排序鍵值映射集合,對key集合進行有序遍歷,使用迭代器Iterator遍歷

(7)concurrentHashMap

concurrentHashMap可以支持併發讀寫功能,首先他不允許key,value爲null,否則拋出空指針異常,底層還是使用數組鏈表紅黑樹的方式實現,在jdk1.8引入CAS和synchronized一起實現線程安全,1.8之前採用設置併發度實現,在源碼中可以看出,他是多個segment[]實現,每個併發度默認爲16,也可以在構造 函數中設置,在程序運行時同時更新concurrentHashMap,並且不產生鎖競爭,整個過程就是獲取segment鎖,在申請鎖之前,put方法會根據trylock的方式嘗試獲得鎖,整個過程會對hashcode的鏈表進行遍歷,如果找不到與key相同的hashentry節點,會提前創建一個hashentry,當trylock一直無法獲取鎖,則會通過lock申請鎖,但是,如果併發度設置小,會帶來嚴重的鎖競爭問題,如果設置小了,或導致segment擴散,導致CPU命中率下降,程序性能下降。1.8引入CAS算法,同時增加一些輔助類如treebin,travesrserde 等對象的內部類,如在中添加過程中,首先會判斷保存鍵值對的數組是不是第一次添加元素,如果是則進行初始化操作,通過計算hash值來確定存儲位置,保證數組不越界,如果沒有初始化,則取出該節點,判斷這個節點有沒有元素,沒有則嘗試通過CAS方式嘗試添加,像代碼中的U對象,如u.cmpareAndSwapObject,直接操作內存來保證線程安全,是一種硬件的安全機制,這個時候沒有加鎖,如果該節點有值,則需要判斷是否需要擴容,複製元素到新數組,也有就是該節點不爲空也不需要擴容,則通過synchronized來加鎖,進行添加,先判斷這個節點時存放鏈表還是樹,是鏈表,則遍歷鏈表,用key和要存放的key比較,一樣就替換,否則添加到鏈表末尾,如果是樹,則調用putTreeVal添加,再添加完成之後,判斷存儲之前該節點的節點數,超過8就調用treeifBin來嘗試將鏈表轉爲樹,否則數組擴容,對於插敘就方便多了,如果key,value 爲空,就拋空指針異常,否則根據key遍歷節點返回value。

以上就是個人對集合方面的整理,還望大家謝指點指點......

 

 

 

 

 

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