「BAT常問」40道Java基礎面試題及詳細答案整理匯總「9—16」

更多技術分享,請點擊右上角紅色的"關注",感謝你的支持!

本文接上篇, ,繼續分享「9—16」的面試題目。

「BAT常問」40道Java基礎面試題及詳細答案整理彙總「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()方法。

代碼如下:

「BAT常問」40道Java基礎面試題及詳細答案整理彙總「9—16」

「BAT常問」40道Java基礎面試題及詳細答案整理彙總「9—16」

輸出結果: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。

「BAT常問」40道Java基礎面試題及詳細答案整理彙總「9—16」

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方法

「BAT常問」40道Java基礎面試題及詳細答案整理彙總「9—16」

4、HashTable使用Enumeration,HashMap使用Iterator

從內部機制實現上的區別如下:

  1. 哈希值的使用不同,Hashtable直接使用對象的hashCode

int hash = key.hashCode();

int index = (hash & 0x7FFFFFFF) % tab.length;

而HashMap重新計算hash值,而且用與代替求模:

「BAT常問」40道Java基礎面試題及詳細答案整理彙總「9—16」

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的形式存放在數組中。

「BAT常問」40道Java基礎面試題及詳細答案整理彙總「9—16」

總結一下map.put後的過程

當向 HashMap 中 put 一對鍵值時,它會根據 key的 hashCode 值計算出一個位置, 該位置就是此對象準備往數組中存放的位置。

如果該位置沒有對象存在,就將此對象直接放進數組當中;如果該位置已經有對象存在了,則順著此存在的對象的鏈開始尋找(為了判斷是否是否值相同,map不允許鍵值對重複), 如果此鏈上有對象的話,再去使用 equals方法進行比較,如果對此鏈上的每個對象的 equals 方法比較都為 false,則將該對象放到數組當中,然後將數組中該位置以前存在的那個對象鏈接到此對象的後面。

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有了足夠的優化吧。

「BAT常問」40道Java基礎面試題及詳細答案整理彙總「9—16」


分享到:


相關文章: