更多技術分享,請點擊右上角紅色的"關注",感謝你的支持!
本文接上篇, ,繼續分享「9—16」的面試題目。
9.HashMap的hashcode的作用
hashCode的存在主要是用於查找的快捷性,如Hashtable,HashMap等,hashCode是用來在散列存儲結構中確定對象的存儲地址的。
如果兩個對象相同,就是適用於equals(java.lang.Object) 方法,那麼這兩個對象的hashCode一定要相同。
如果對象的equals方法被重寫,那麼對象的hashCode也儘量重寫,並且產生hashCode使用的對象,一定要和equals方法中使用的一致,否則就會違反上面提到的第2點。
兩個對象的hashCode相同,並不一定表示兩個對象就相同,也就是不一定適用於equals(java.lang.Object) 方法,只能夠說明這兩個對象在散列存儲結構中,如Hashtable,他們“存放在同一個籃子裡”。
什麼時候需要重寫?
一般的地方不需要重載hashCode,只有當類需要放在HashTable、HashMap、HashSet等等hash結構的集合時才會重載hashCode,那麼為什麼要重載hashCode呢?
要比較兩個類的內容屬性值,是否相同時候,根據hashCode 重寫規則,重寫類的 指定字段的hashCode(),equals()方法。
代碼如下:
輸出結果:true
上面的方法,做的事情就是,比較兩個集合中的,實體類對象屬性值,是否一致
OrderSum 不在比較範圍內,因為沒有重寫它的,equals()和hashCode()方法
為什麼要重載equal方法?
因為Object的equal方法默認是兩個對象的引用的比較,意思就是指向同一內存,地址則相等,否則不相等;如果你現在需要利用對象裡面的值來判斷是否相等,則重載equal方法。
10.為什麼重載hashCode方法?
一般的地方不需要重載hashCode,只有當類需要放在HashTable、HashMap、HashSet等等hash結構的集合時才會重載hashCode,那麼為什麼要重載hashCode呢?
如果你重寫了equals,比如說是基於對象的內容實現的,而保留hashCode的實現不變,那麼很可能某兩個對象明明是“相等”,而hashCode卻不一樣。
這樣,當你用其中的一個作為鍵保存到hashMap、hasoTable或hashSet中,再以“相等的”找另一個作為鍵值去查找他們的時候,則根本找不到。
為什麼equals()相等,hashCode就一定要相等,而hashCode相等,卻不要求equals相等?
1、因為是按照hashCode來訪問小內存塊,所以hashCode必須相等。
2、HashMap獲取一個對象是比較key的hashCode相等和equal為true。
之所以hashCode相等,卻可以equal不等,就比如ObjectA和ObjectB他們都有屬性name,那麼hashCode都以name計算,所以hashCode一樣,但是兩個對象屬於不同類型,所以equal為false。
為什麼需要hashCode?
1、通過hashCode可以很快的查到小內存塊。
2、通過hashCode比較比equal方法快,當get時先比較hashCode,如果hashCode不同,直接返回false。
11.ArrayList、LinkedList、Vector的區別
List的三個子類的特點
ArrayList:
底層數據結構是數組,查詢快,增刪慢。
線程不安全,效率高。
Vector:
-
底層數據結構是數組,查詢快,增刪慢。
線程安全,效率低。
Vector相對ArrayList查詢慢(線程安全的)。
Vector相對LinkedList增刪慢(數組結構)。
LinkedList
底層數據結構是鏈表,查詢慢,增刪快。
線程不安全,效率高。
Vector和ArrayList的區別
Vector是線程安全的,效率低。
ArrayList是線程不安全的,效率高。
共同點:底層數據結構都是數組實現的,查詢快,增刪慢。
ArrayList和LinkedList的區別
ArrayList底層是數組結果,查詢和修改快。
LinkedList底層是鏈表結構的,增和刪比較快,查詢和修改比較慢。
共同點:都是線程不安全的
List有三個子類使用
查詢多用ArrayList。
增刪多用LinkedList。
如果都多ArrayList。
12.String、StringBuffer與StringBuilder的區別
String:適用於少量的字符串操作的情況。StringBuilder:適用於單線程下在字符緩衝區進行大量操作的情況。StringBuffer:適用多線程下在字符緩衝區進行大量操作的情況。StringBuilder:是線程不安全的,而StringBuffer是線程安全的。
這三個類之間的區別主要是在兩個方面,即運行速度和線程安全這兩方面。首先說運行速度,或者說是執行速度,在這方面運行速度快慢為:StringBuilder > StringBuffer > String。
String最慢的原因
String為字符串常量,而StringBuilder和StringBuffer均為字符串變量,即String對象一旦創建之後該對象是不可更改的,但後兩者的對象是變量,是可以更改的。
再來說線程安全
在線程安全上,StringBuilder是線程不安全的,而StringBuffer是線程安全的。
如果一個StringBuffer對象在字符串緩衝區被多個線程使用時,StringBuffer中很多方法可以帶有synchronized關鍵字,所以可以保證線程是安全的,但StringBuilder的方法則沒有該關鍵字,所以不能保證線程安全,有可能會出現一些錯誤的操作。所以如果要進行的操作是多線程的,那麼就要使用StringBuffer,但是在單線程的情況下,還是建議使用速度比較快的StringBuilder。
13.Map、Set、List、Queue、Stack的特點與用法
Map
Map是鍵值對,鍵Key是唯一不能重複的,一個鍵對應一個值,值可以重複。
TreeMap可以保證順序。
HashMap不保證順序,即為無序的。
Map中可以將Key和Value單獨抽取出來,其中KeySet()方法可以將所有的keys抽取正一個Set。而Values()方法可以將map中所有的values抽取成一個集合。
Set
不包含重複元素的集合,set中最多包含一個null元素。
只能用Lterator實現單項遍歷,Set中沒有同步方法。
List
有序的可重複集合。
可以在任意位置增加刪除元素。
用Iterator實現單向遍歷,也可用ListIterator實現雙向遍歷。
Queue
Queue遵從先進先出原則。
使用時儘量避免add()和remove()方法,而是使用offer()來添加元素,使用poll()來移除元素,它的優點是可以通過返回值來判斷是否成功。
LinkedList實現了Queue接口。
Queue通常不允許插入null元素。
Stack
Stack遵從後進先出原則。
Stack繼承自Vector。
它通過五個操作對類Vector進行擴展,允許將向量視為堆棧,它提供了通常的push和pop操作,以及取堆棧頂點的peek()方法、測試堆棧是否為空的empty方法等。
用法
如果涉及堆棧,隊列等操作,建議使用List。
對於快速插入和刪除元素的,建議使用LinkedList。
如果需要快速隨機訪問元素的,建議使用ArrayList。
更為精煉的總結
Collection 是對象集合, Collection 有兩個子接口 List 和 Set
List 可以通過下標 (1,2..) 來取得值,值可以重複。Set 只能通過遊標來取值,並且值是不能重複的。
ArrayList , Vector , LinkedList 是 List 的實現類
ArrayList 是線程不安全的, Vector 是線程安全的,這兩個類底層都是由數組實現的。
LinkedList 是線程不安全的,底層是由鏈表實現的。
Map 是鍵值對集合
HashTable 和 HashMap 是 Map 的實現類。
HashTable 是線程安全的,不能存儲 null 值。
HashMap 不是線程安全的,可以存儲 null 值。
Stack類:繼承自Vector,實現一個後進先出的棧。提供了幾個基本方法,push、pop、peak、empty、search等。
Queue接口:提供了幾個基本方法,offer、poll、peek等。已知實現類有LinkedList、PriorityQueue等。
14.HashMap和HashTable的區別
Hashtable是基於陳舊的Dictionary類的,HashMap是Java 1.2引進的Map接口的一個實現,它們都是集合中將數據無序存放的。
1、hashMap去掉了HashTable?的contains方法,但是加上了containsValue()和containsKey()方法
HashTable Synchronize同步的,線程安全,HashMap不允許空鍵值為空?,效率低。HashMap 非Synchronize線程同步的,線程不安全,HashMap允許空鍵值為空?,效率高。
Hashtable是基於陳舊的Dictionary類的,HashMap是Java 1.2引進的Map接口的一個實現,它們都是集合中將數據無序存放的。Hashtable的方法是同步的,HashMap未經同步,所以在多線程場合要手動同步HashMap這個區別就像Vector和ArrayList一樣。
查看Hashtable的源代碼就可以發現,除構造函數外,Hashtable的所有 public 方法聲明中都有 synchronized 關鍵字,而HashMap的源代碼中則連 synchronized 的影子都沒有,當然,註釋除外。
2、Hashtable不允許 null 值(key 和 value 都不可以),HashMap允許 null 值(key和value都可以)。
3、兩者的遍歷方式大同小異,Hashtable僅僅比HashMap多一個elements方法。
4、HashTable使用Enumeration,HashMap使用Iterator
從內部機制實現上的區別如下:
哈希值的使用不同,Hashtable直接使用對象的hashCode
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
而HashMap重新計算hash值,而且用與代替求模:
2.Hashtable中hash數組默認大小是11,增加的方式是 old*2+1。HashMap中hash數組的默認大小是16,而且一定是2的指數。
15.JDK7與JDK8中HashMap的實現
JDK7中的HashMap
HashMap底層維護一個數組,數組中的每一項都是一個Entry。
transient Entry
[] table;
我們向 HashMap 中所放置的對象實際上是存儲在該數組當中。 而Map中的key,value則以Entry的形式存放在數組中。
總結一下map.put後的過程:
當向 HashMap 中 put 一對鍵值時,它會根據 key的 hashCode 值計算出一個位置, 該位置就是此對象準備往數組中存放的位置。
如果該位置沒有對象存在,就將此對象直接放進數組當中;如果該位置已經有對象存在了,則順著此存在的對象的鏈開始尋找(為了判斷是否是否值相同,map不允許
JDK8中的HashMap
JDK8中採用的是位桶+鏈表/紅黑樹(有關紅黑樹請查看紅黑樹)的方式,也是非線程安全的。當某個位桶的鏈表的長度達到某個閥值的時候,這個鏈表就將轉換成紅黑樹。
JDK8中,當同一個hash值的節點數不小於8時,將不再以單鏈表的形式存儲了,會被調整成一顆紅黑樹(上圖中null節點沒畫)。這就是JDK7與JDK8中HashMap實現的最大區別。
接下來,我們來看下JDK8中HashMap的源碼實現。
JDK中Entry的名字變成了Node,原因是和紅黑樹的實現TreeNode相關聯。
transient Node
[] table;
當衝突節點數不小於8-1時,轉換成紅黑樹。
staticfinalint TREEIFY_THRESHOLD = 8;
16.HashMap和ConcurrentHashMap的區別,HashMap的底層源碼
為了線程安全從ConcurrentHashMap代碼中可以看出,它引入了一個“分段鎖”的概念,具體可以理解為把一個大的Map拆分成N個小的HashTable,根據key.hashCode()來決定把key放到哪個HashTable中。
Hashmap本質是數組加鏈表。根據key取得hash值,然後計算出數組下標,如果多個key對應到同一個下標,就用鏈表串起來,新插入的在前面。
ConcurrentHashMap:在hashMap的基礎上,ConcurrentHashMap將數據分為多個segment,默認16個(concurrency level),然後每次操作對一個segment加鎖,避免多線程鎖的幾率,提高併發效率。
總結
JDK6,7中的ConcurrentHashmap主要使用Segment來實現減小鎖粒度,把HashMap分割成若干個Segment,在put的時候需要鎖住Segment,get時候不加鎖,使用volatile來保證可見性,當要統計全局時(比如size),首先會嘗試多次計算modcount來確定,這幾次嘗試中,是否有其他線程進行了修改操作,如果沒有,則直接返回size。如果有,則需要依次鎖住所有的Segment來計算。
jdk7中ConcurrentHashmap中,當長度過長碰撞會很頻繁,鏈表的增改刪查操作都會消耗很長的時間,影響性能。
jdk8 中完全重寫了concurrentHashmap,代碼量從原來的1000多行變成了 6000多 行,實現上也和原來的分段式存儲有很大的區別。
JDK8中採用的是位桶+鏈表/紅黑樹(有關紅黑樹請查看紅黑樹)的方式,也是非線程安全的 。當某個位桶的鏈表的長度達到某個閥值的時候,這個鏈表就將轉換成紅黑樹。
JDK8中,當同一個hash值的節點數不小於8時,將不再以單鏈表的形式存儲了,會被調整成一顆紅黑樹(上圖中null節點沒畫)。這就是JDK7與JDK8中HashMap實現的最大區別。
主要設計上的變化有以下幾點
1.jdk8不採用segment而採用node,鎖住node來實現減小鎖粒度。2.設計了MOVED狀態 當resize的中過程中 線程2還在put數據,線程2會幫助resize。3.使用3個CAS操作來確保node的一些操作的原子性,這種方式代替了鎖。4.sizeCtl的不同值來代表不同含義,起到了控制的作用。
至於為什麼JDK8中使用synchronized而不是ReentrantLock,我猜是因為JDK8中對synchronized有了足夠的優化吧。
閱讀更多 程序碼上說 的文章