java 基礎 --- 2
1, Object有哪些公用方法?
答: object 的公有方法主要有 equals , hasCode , toString 等這類基本方法, 此外還有一些關於併發方面的方法, 如 wait, notify, notifyAll , 最值得我們思考的就是這幾個方法, 也許我們會疑惑, 為什麼關於併發方面的方法會被設計成基類的方法, 而不是 Thread 這類專門用來併發的類的方法呢? 這裡我們要從這幾個方法的意義去思考了, 這幾個方法是用來進行執行緒同步的, 而之所以要進行執行緒同步, 是因為儲存在堆區共享資源被多個執行緒所訪問而可能導致的執行緒不安全的因素, 而任何物件都可以成為位於堆區的共享資源, 所以這些方法被設計成 object 的方法自然是合理的.
2, Java集合框架中有哪些類?都有什麼特點?
答: 主要有兩大類, Collection 和 Map, 這兩大類分別有如下常用的小類:
collection:
- List 介面, 如名字一樣, 這是一個 列表 集合, 按照新增元素的順序儲存.
- Set 介面, 其不能儲存重複值, 具體實現不重複的細節是使用 hasCode 和 equals 方法, 這個介面的功能實現絕大部分都由 map 的實現子類來實現.
- Queue 介面, 如名字一樣, 這是一個佇列介面, LinkedList 實現了這個介面, 可以用來實現佇列的行為
map:
map 沒有中間介面, 其和 Collection 一樣, 都有很多 abstract 中間類, 但是這些類我們通常都可以忽列掉.
- HashMap , 儲存和查詢速度最快, 其採用了 雜湊 機制, 初始容量為 16, 預設負載因子為 0.75, 它應該是我們在使用 Map 的預設選擇.
- TreeMap, 有序的Map, 按照比較結果的自然排序方式進行排序, 其內部元素要實現 comparable 介面.
- LinkedHashMap, 擁有和 HasMap 媲美的查詢速度, 其可以按照新增元素的順序儲存元素, 其實現了 LRU 演算法, 通過構造方法可以使用這個特性.
3, 集合、陣列、泛型的關係,並比較
答: 集合的底層實現離不開陣列, 集合和陣列都是一種儲存資料的容器, 但是相對於陣列, 集合更加靈活, 其可以自動擴容, 擴容方式一般是通過對原陣列的複製, 雖然我們自己也可以實現類似的行為, 但肯定沒有官方的優秀. 泛型 是 java 中一個很重要的特性, 在 JDK 1.5 引入的, 其作用是為了能夠在編譯期就能夠發現某些型別錯誤, 從而提高程式的安全性. 集合中一般都需要指定泛型來確定元素的型別, 但是不能建立泛型陣列, 之所以不能建立是因為這違背了 泛型 引入的最初目的, 即在編譯期就能發現某些型別錯誤,
4, ArrayList和LinkList的區別?
答: ArrayList 底層實現的資料結構是陣列, 初始容量為 10, LinkList 底層實現為雙向連結串列.
5, HashSet和TreeSet的區別?
答: HashSet 由 HashMap 實現, 其具體的行為特徵和 HashMap 類似, TreeSet 由 TreeMap 實現, 對元素按照自然排序, 因為其內部支援排序, TreeSet 迭代遍歷的速度比 HashSet 稍快.
6, HashMap和Hashtable的區別?
答: Hashtable 是 HashMap 的執行緒安全實現. 大量的方法使用了 synchronized, 所以導致其效率較為低下, 我們不應該在現在的編碼中使用它, 它是JDK 1.0 中較老的容器類, 我們應該使用 ConcurrentHashMap 來實現併發訪問, 其採用了分段鎖的技術, 比 HashTable 效率要高.
7, ArrayList和Vector的區別?
答: Vector 是 ArrayList 的執行緒安全版, 其和 HashTable 一樣, 大量的方法都使用了 sync 關鍵字, 其效率較為低下, 也為 JDK 1.0 中較老的容器類, 我們應該使用 CopyOnWriteArrayList 來替換他, 其 採用了讀寫分離的思想, 併發效率得到了改善.
8, 如何解決Hash衝突?
答: 常見的解決 Hash 衝突的方法有:
- 線性探測法. --- 最簡單的探測方法, 從發生衝突的地方開始, 進行線性探測, 逐個往後探測.
- 平方探測法. --- 從發生衝突的地方, 依次加上自然數的平方, 直到找到空閒區間.
- 雙雜湊函式探測法. --- 使用第二個雜湊函式對關鍵字再雜湊, 然後以這個值為步長進行探測.
- 鏈地址法. --- 採用區域性連結串列來維持衝突, JDK 1.7 中的 HashMap 就是如此, 而 1.8 則採用了紅黑樹.
- 再 Hash 法. --- 發生衝突, 使用第二個雜湊函式再雜湊.
- 建立公共溢位區間. --- 建立一個衝突表, 單獨用來存放發生衝突的元素.
9. HashMap在put、get元素的過程?體現了什麼資料結構?
答: get 過程:
- 根據傳入的 key, 獲取其 hashCode,
- 然後根據 hashCode 得到地址的索引
- 判斷該地址索引上的筒位是否為 null,如果為 null,直接返回, 如果不為 null, 比較 key 的 hashCode 和是否相等.
- 然後比較 key 是否相等, 相等則返回, 如果不相等則獲取該筒位的 next 筒位.(HashMap 是採用區域性連結串列來維持衝突的)
put 過程:
- 獲取 hashCode, 然後得到地址索引.
- 判斷該地址上的筒位是否為 null, 如果為 null,直接 new 一個新的 node, 並儲存 key 和 value, 然後判斷是否需要擴容, 最後返回 null.
- 如果不為 null, 可能是產生了衝突, 也可能是需要更新這個 key 原來對應的 value.
- 判斷 key 是否相等, 如果相等, 更新 value 並返回.
- 如果不相等,一直遍歷到這個區域性衝突連結串列的尾部, 然後插入這個節點.
這兩個過程都表明了, HashMap 中, 採用了連結串列來維持 hash 衝突, 對於 node 的儲存, 則是採用的 陣列, 但是陣列中的每一個元素都是一條連結串列的第一個節點.
10, 如何保證HashMap執行緒安全?什麼原理?
答:
- 使用 Collections.synchronizedMap() 其實現原理還是大量的使用了 sync 關鍵字, 在絕大部分方法中都採用了一個 object 物件來作為鎖.
- 使用 Hashtable, 其效率低下, 因為大量的方法使用了 sync 關鍵字.
- 使用 ConcurrentHashMap, 在 JDK 7 中其採用了分段鎖的技術, 即不對整個 hash 表加鎖, 把整個 hash 表分為多個 Segment 片段, 每個 Segment 都是繼承自 ReentrantLock , 從而很容易實現分別加鎖, 提高併發效率, 在 JDK 8 中則採用了基於機器級別上的CAS 原子性操作, 底層的資料結構採用了陣列和紅黑樹.
HashMap 在併發環境中, 在 HashMap 擴容的 reHash 中, 會導致環形連結串列, 從而在 get 的元素時進入死迴圈. 具體部落格: HashMap 死迴圈
11, HashMap是有序的嗎?如何實現有序?
答: HashMap 預設是無需的, 其具體位置和 key 的 hasCode 和 map 中陣列長度有關, 如果要實現有序, 可以使用 linkedHashMap, 其實現有序的原理是 LinkedHashMap 對 Node 進行了重寫, 增加了 before 和 next 這兩個成員變數, 其在插入元素的時候, 通過這兩個引用構成了一個雙向連結串列, 從而可以實現在遍歷元素的有序狀態.
12, hashcode()的作用,與equal()有什麼區別?
答: 一些常見的基於 hash 原理的雜湊演算法和資料結構都需要使用到 hashCode, hashCode 主要作用是用於在為這些基於 hash 演算法的資料結構提供支援, 其與 equals 的區別是, 返回值不同, 具體的含義也不同, 一個是比較兩個物件, 一個是獲取這個物件的一個特有的屬性值.
13, HashMap是如何擴容的?如何避免擴容?
答: HashMap 在新新增一個元素後, 如果尺寸大於負載量, 則會進行擴容, 其擴容的大致過程為重新建立一個數組長度是原陣列兩倍的陣列, 然後遍歷原陣列中的每一個元素, 分別進行再 hash 然後放入到新的陣列中, 因此, HashMap 的擴容過程還是有點開銷的, 如果我們在一開始建立 HashMap 的時候可以預估元素的數量, 便可以通過構造方法傳入, 從而減少擴容的可能性和次數, 另外, 還可以通過提高負載因子來減少擴容, 但是此方法會到這查詢效率變低, 不太值得.