你經歷過哪些有意思的面試題目?
一、String 原理,String 、StringBuffer、StringBuilder區別。
String是final類,屬于不可變字符串,采用char數組。
StringBuffer是線程安全的,內部采用synchronized。
StringBuilder是非線程安全的。
二、String與StringBuilder拼接字符串哪個性能好,為什么?
StringBuilder性能比較好,String在拼接的時候會new出多個對象,消耗資源。尤其是在for循環下進行拼接性能差距很明顯。
三、接口與抽象類的區別
1. 接口的方法默認是 public,所有方法在接口中不能有實現(Java 8 開始接口方法可以有默認實現),抽象類可以有非抽象的方法
2. 接口中的實例變量默認是 final 類型的,而抽象類中則不一定
3. 一個類可以實現多個接口,但最多只能實現一個抽象類
4. 一個類實現接口的話要實現接口的所有方法,而抽象類不一定
5. 接口不能用 new 實例化,但可以聲明,但是必須引用一個實現該接口的對象 從設計面來說,抽象是對類的抽象,是一種模板設計,接口是行為的抽象,是一種行為的規范。
四、重載與重寫的區別
重載(Overload)是讓類以統一的方式處理不同類型數據的一種手段,實質表現就是多個具有不同的參數個數或者類型的同名函數(返回值類型可隨意,不能以返回類型作為重載函數的區分標準)同時存在于同一個類中,是一個類中多態性的一種表現(調用方法時通過傳遞不同參數個數和參數類型來決定具體使用哪個方法的多態性)。
重寫(Override)是父類與子類之間的多態性,實質是對父類的函數進行重新定義,如果在子類中定義某方法與其父類有相同的名稱和參數則該方法被重寫,不過子類函數的訪問修飾權限不能小于父類的;若子類中的方法與父類中的某一方法具有相同的方法名、返回類型和參數表,則新方法將覆蓋原有的方法,如需父類中原有的方法則可使用 super 關鍵字。
五、數組與鏈表的區別
1.數組是一種線性表數據結構。它用一組連續的內存空間,來存儲一組具有相同類型的數據。最大的特點就是支持隨機訪問,但插入、刪除操作也因此變得比較低效,平均情況時間復雜度為O(n)。
2.鏈表它并不需要一塊連續的內存空間,它通過“指針”(前后節點指向)將一組零散的內存,空間可擴容,比較常用的是單鏈表,雙鏈表和循環鏈表。和數組相比,鏈表更適合插入、刪除操作頻繁的場景,查詢的時間復雜度較高。
六、介紹一下ArrayList
1、ArrayList底層數據結構是一個數組,數組元素類型為Object,對ArrayList的所有操作都是基于數組。
2、ArrayList不是線程安全的。對ArrayList進行添加元素的操作的時候是分兩個步驟進行的,即第一步先在object[size]的位置上存放需要添加的元素;第二步將size的值增加1。由于這個過程在多線程的環境下是不能保證具有原子性的,因此ArrayList在多線程的環境下是線程不安全的。
如果非要在多線程的環境下使用ArrayList,就需要保證它的線程安全性,通常有兩種解決辦法:第一,使用synchronized關鍵字;第二,可以用Collections類中的靜態方法synchronizedList();
3、ArrayList默認大小為10
4、ArrayList擴容機制:
第一,在add()方法中調用ensureCapacityInternal(size + 1)方法來確定集合確保添加元素成功的最小集合容量minCapacity的值。參數為size+1,代表的含義是如果集合添加元素成功后,集合中的實際元素個數。換句話說,集合為了確保添加元素成功,那么集合的最小容量minCapacity應該是size+1。在ensureCapacityInternal方法中,首先判斷elementData是否為默認的空數組,如果是,minCapacity為minCapacity與集合默認容量大小中的較大值。
第二,調用ensureExplicitCapacity(minCapacity)方法來確定集合為了確保添加元素成功是否需要對現有的元素數組進行擴容。首先將結構性修改計數器加一;然后判斷minCapacity與當前元素數組的長度的大小,如果minCapacity比當前元素數組的長度的大小大的時候需要擴容,進入第三階段。
第三,如果需要對現有的元素數組進行擴容,則調用grow(minCapacity)方法,參數minCapacity表示集合為了確保添加元素成功的最小容量。在擴容的時候,首先將原元素數組的長度增大1.5倍(oldCapacity + (oldCapacity >> 1)),然后對擴容后的容量與minCapacity進行比較:① 新容量小于minCapacity,則將新容量設為minCapacity;②新容量大于minCapacity,則指定新容量。最后將舊數組拷貝到擴容后的新數組中。
七、ArrayList在遍歷的時候可以刪除數據嗎?
for循環遍歷不可以,會報ConcurrentModificationException異常,迭代器Iterator下可以。
內部有一個modCount 進行修改次數檢查。
如果沒checkForComodification去檢查expectedModCount與modCount相等,這個程序肯定會報ArrayIndexOutOfBoundsException
八、LinkedList底層數據結構是什么?說明ArrayList,LinkedList二者區別?
1.ArrayList是實現了基于動態數組的數據結構,LinkedList基于鏈表的數據結構每個元素都包含了 上一個和下一個元素的引用,所以add/remove 只會影響到上一個和下一個元素,。
2.對于隨機訪問get和set,ArrayList覺得優于LinkedList,因為LinkedList要移動指針。
3.對于新增和刪除操作add和remove,LinkedList比較占優勢,因為ArrayList要移動數據。
九、簡單介紹一下HashMap?
1、負載因子0.75 , 提高空間利用率和 減少查詢成本的折中,主要是泊松分布,0.75的話碰撞最小。
2、擾動函數就是解決碰撞問題,減少碰撞。
3、hasMap的容量盡量是2的倍數,這樣使散列均勻,不易出現hash沖突
HashMap默認初始化大小16,數據結構是一個數組+鏈表+紅黑樹(平衡二叉樹紅黑節點)。
Put的時候首先拿到hash值看是否存在值如果沒有值直接放入,存在則進行比較是否相等,不等生成鏈表,當鏈表大于8的時候生成紅黑樹。
HashMap是線程不安全的。
十、介紹一下ConcurrentHashMap
concurrentHashMap是一個線程安全的高可用HashMap。
1.8拋棄了之前的segement分段上鎖,采用了CAS+synchronized來保證并發安全,并且吧put的流程更加細化,而且resize的時候不會阻塞任何一個線程。
重要參數:
MIN_TRANSFER_STRIDE: 意思是resize時候每個線程每次最小的搬運量(搬運多少個entry),范圍會繼續細分以便可以允許多個resize線程。
RESIZE_STAMP_BITS :位的個數來用來生成戳,用在sizeCtrl里面;
MAX_RESIZERS:最大參與resize的線程數量;
RESIZE_STAMP_SHIFT:用來記錄在sizeCtrl中size戳的位偏移數。
Put主邏輯:
1、首先判斷key\value都不能為null
2、進行自旋操作,每次把當前的table賦給tab
3、計算key的hash
4、如果當前table沒有初始化,現代用initTable方法將tab初始化。
5、Tab索引i的位置元素為null,則直接使用CAS插入
6、判斷是否在resize(擴容),在擴容走helpTransfer - transfer
7、沒有resize走正常put
8、用synchronized針對單個節點加鎖
9、判斷節點hash(正常node大于0,樹為-2)
10、正常節點插入,插入到隊尾
11、樹節點插入
initTable方法
1、自旋操作每次針對tab賦值而且判斷長度
2、如果搶鎖失敗(sizeCtl作為自旋鎖使用),則告訴cpu讓出時間片(Thread.yield())
3、搶鎖成功,再次檢測tab的賦值以及數組長度
4、SizeCtl置為n的0.75
5、finally里面邏輯,sizeCtl賦值并解鎖
addCount方法
增加表容量的計數,如果這個表需要resize但還沒開始resize,則初始化transfer(這個東西用來進行resize)。如果已經開始resize了,則看看需不需要加入幫助一起resize。然后重新檢查表的計數看看需不需要再次resize
transfer方法(resize用此方法):
原理就是讓每一個put完的線程,或者想要put的線程到了整個表需要resize的時候都過來進入resize流水線工作,直到有一個線程說自己已經全部弄完了,方法才能返回。
以前的正常設計是resize的時候整個表就阻塞住了,但是現在resize的時候,想要操作的線程都會來參與一起resize,這么一來其他的線程就不用干等著了。
參考文檔:https://www.cnblogs.com/kobebyrant/p/11296309.html
自旋鎖:
自旋鎖?它是為實現保護共享資源而提出一種鎖機制。其實,自旋鎖與互斥鎖比較類似,它們都是為了解決對某項資源的互斥使用。無論是互斥鎖,還是自旋鎖,在任何時刻,最多只能有一個保持者,也就說,在任何時刻最多只能有一個執行單元獲得鎖。但是兩者在調度機制上略有不同。對于互斥鎖,如果資源已經被占用,資源申請者只能進入睡眠狀態。但是自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,調用者就一直循環在那里看是否該自旋鎖的保持者已經釋放了鎖,"自旋"一詞就是因此而得名。
十一、比較各種集合
HashMap與TreeMap:
1、都是非線程安全
2、HashMap通過hashCode進行快速查找,無序;treeMap有序。
3、hashMap基于哈希實現,treeMap基于紅黑樹實現
4、hashMap適用于Map的插入刪除和定位元素,treeMap適用于順序遍歷key
5、HashMap比TreeMap快一點,建議使用hashMap,需要排序的時候再用treeMap。
HashSet與ArrayList:
1.HashSet
1) HashSet不能夠存儲相同的元素,元素是否相同的判斷:重寫元素的equals方法。equals方法和hashCode方法必須兼容,如:equals方法判斷的是用戶的名字name,那么hashCode的返回的hashcode必須是name。hashcode();
2) HashSet存儲是無序的,保存的順序與添加的順序是不一致的,它不是線性結構,而是散列結構,(通過散列表:散列單元指向鏈表)。因此,HashSet的查詢效率相對比較高。
3) HashSet不是線程安全的,不是線程同步的。這需要自己實現線程同步:Collections.synchronizedCollection(),方法實現。
4) Haset底層用了HashMap用key做的HashSet的value,迭代器循環代碼:map.keySet().iterator()
2.ArrayList
1) ArrayList中存放順序和添加順序是一致的。并且可重復元素。
2) 不是線程安全的,不是線程同步的。
3) ArrayList是通過可變大小的數組實現的,允許null在內的所有元素。
4) ArrayList適合通過位子來讀取元素。