1. 程式人生 > >03.Java資料結構問題

03.Java資料結構問題

目錄介紹

  • 3.0.0.1 在arrayList中System.arraycopy()和Arrays.copyOf()方法區別聯絡?System.arraycopy()和Arrays.copyOf()程式碼說明?
  • 3.0.0.2 SparseArray基本介紹,相比HashMap為什麼效能會好?
  • 3.0.0.3 Arrays和Collections 對於sort的不同實現原理?說一說它們的區別……
  • 3.0.0.4 Java集合框架中有哪些類?都有什麼特點?Java集合的快速失敗機制 “fail-fast”?
  • 3.0.0.5 ArrayList,Vector和LinkList的區別,底層分別是怎麼實現的,儲存空間是如何擴容的?什麼是載入因子?
  • 3.0.0.6 如何理解ArrayList的擴容消耗?Arrays.asList方法後的List可以擴容嗎?ArrayList如何序列化?
  • 3.0.0.7 如何理解list集合讀寫機制和讀寫效率?什麼是CopyOnWriteArrayList,它與ArrayList有何不同?
  • 3.0.1.0 HashSet和TreeSet的區別?是如何保證唯一值的,底層怎麼做到的?
  • 3.0.1.5 HashMap和Hashtable的區別?HashMap在put、get元素的過程?體現了什麼資料結構?
  • 3.0.1.6 如何保證HashMap執行緒安全?底層怎麼實現的?HashMap是有序的嗎?如何實現有序?
  • 3.0.1.7 HashMap儲存兩個物件的hashcode相同會發生什麼?如果兩個鍵的hashcode相同,你如何獲取值物件?
  • 3.0.1.8 HashMap為什麼不直接使用hashCode()處理後的雜湊值直接作為table的下標?
  • 3.0.1.9 為什麼HashMap中String、Integer這樣的包裝類適合作為K?為啥不用其他作key值?
  • 3.0.2.0 HashMap是如何擴容的?如何理解HashMap的大小超過了負載因子定義的容量?重新調整HashMap大小存在什麼問題嗎?

好訊息

  • 部落格筆記大彙總【15年10月到至今】,包括Java基礎及深入知識點,Android技術部落格,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護並且修正,持續完善……開源的檔案是markdown格式的!同時也開源了生活部落格,從12年起,積累共計500篇[近100萬字],將會陸續發表到網上,轉載請註明出處,謝謝!
  • 連結地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起於忽微,量變引起質變!所有部落格將陸續開源到GitHub!

3.0.0.1 在arrayList中System.arraycopy()和Arrays.copyOf()方法區別聯絡?System.arraycopy()和Arrays.copyOf()程式碼說明?

  • System.arraycopy()和Arrays.copyOf()方法區別?
    • 比如下面add(int index, E element)方法就很巧妙的用到了arraycopy()方法讓陣列自己複製自己實現讓index開始之後的所有成員後移一個位置:
      /**
       * 在此列表中的指定位置插入指定的元素。 
       * 先呼叫 rangeCheckForAdd 對index進行界限檢查;然後呼叫 ensureCapacityInternal 方法保證capacity足夠大; * 再將從index開始之後的所有成員後移一個位置;將element插入index位置;最後size加1。 */ public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); //arraycopy()方法實現陣列自己複製自己 //elementData:源陣列;index:源陣列中的起始位置;elementData:目標陣列;index + 1:目標陣列中的起始位置; size - index:要複製的陣列元素的數量; System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } 
    • 如toArray()方法中用到了copyOf()方法
      /**
       *以正確的順序(從第一個到最後一個元素)返回一個包含此列表中所有元素的陣列。 
       *返回的陣列將是“安全的”,因為該列表不保留對它的引用。 (換句話說,這個方法必須分配一個新的陣列)。
       *因此,呼叫者可以自由地修改返回的陣列。 此方法充當基於陣列和基於集合的API之間的橋樑。 */ public Object[] toArray() { //elementData:要複製的陣列;size:要複製的長度 return Arrays.copyOf(elementData, size); } 
    • 兩者聯絡與區別
      • 看了上面的兩者原始碼可以發現copyOf()內部呼叫了System.arraycopy()方法
      • 技術部落格大總結
      • 區別:
        • 1.arraycopy()需要目標陣列,將原陣列拷貝到你自己定義的數組裡,而且可以選擇拷貝的起點和長度以及放入新陣列中的位置
        • 2.copyOf()是系統自動在內部新建一個數組,並返回該陣列。
  • System.arraycopy()和Arrays.copyOf()程式碼說明?
    • 使用System.arraycopy()方法
      public static void main(String[] args) {
      	// TODO Auto-generated method stub
      	int[] a = new int[10];
      	a[0] = 0;
      	a[1] = 1; a[2] = 2; a[3] = 3; System.arraycopy(a, 2, a, 3, 3); a[2]=99; for (int i = 0; i < a.length; i++) { System.out.println(a[i]); } } //結果: //0 1 99 2 3 0 0 0 0 0 
    • 使用Arrays.copyOf()方法。技術部落格大總結
      public static void main(String[] args) {
      	int[] a = new int[3]; a[0] = 0; a[1] = 1; a[2] = 2; int[] b = Arrays.copyOf(a, 10); System.out.println("b.length"+b.length); //結果: //10 } 
    • 得出結論
      • arraycopy() 需要目標陣列,將原陣列拷貝到你自己定義的數組裡或者原陣列,而且可以選擇拷貝的起點和長度以及放入新陣列中的位置 copyOf() 是系統自動在內部新建一個數組,並返回該陣列。

3.0.0.2 SparseArray基本介紹,相比HashMap為什麼效能會好?

  • 位於android.util,Android 中的資料結構,針對移動端做了優化,在資料量比較少的情況下,效能會好過 HashMap,類似於 HashMap,key:int ,value:object 。
  • 1.key 和 value 採用陣列進行儲存。儲存 key 的陣列是 int 型別,不需要進行裝箱操作。提供了速度。
  • 2.採用二分查詢法,在插入進行了排序,所以兩個陣列是按照從小到大進行排序的。
  • 3.在查詢的時候,進行二分查詢,資料量少的情況下,速度比較快。

3.0.0.3 Arrays和Collections 對於sort的不同實現原理?說一說它們的區別……

  • 1、Arrays.sort()
    • 該演算法是一個經過調優的快速排序,此演算法在很多資料集上提供N*log(N)的效能,這導致其他快速排序會降低二次型效能。
  • 2、Collections.sort()
    • 該演算法是一個經過修改的合併排序演算法(其中,如果低子列表中的最高元素效益高子列表中的最低元素,則忽略合併)。此演算法可提供保證的N*log(N)的效能,此實現將指定列表轉儲到一個數組中,然後再對陣列進行排序,在重置陣列中相應位置處每個元素的列表上進行迭代。
  • 它們的區別

3.0.0.4 Java集合框架中有哪些類?都有什麼特點?Java集合的快速失敗機制 “fail-fast”?

  • 可將Java集合框架大致可分為Set、List、Queue 和Map四種體系
    • Set:代表無序、不可重複的集合,常見的類如HashSet、TreeSet
    • List:代表有序、可重複的集合,常見的類如動態陣列ArrayList、雙向連結串列LinkedList、可變陣列Vector
    • Map:代表具有對映關係的集合,常見的類如HashMap、LinkedHashMap、TreeMap
    • Queue:代表一種佇列集合
  • Java集合的快速失敗機制 “fail-fast”
    • 技術部落格大總結
    • java集合的一種錯誤檢測機制,當多個執行緒對集合進行結構上的改變的操作時,有可能會產生 fail-fast 機制。
      • 例如:假設存在兩個執行緒(執行緒1、執行緒2),執行緒1通過Iterator在遍歷集合A中的元素,在某個時候執行緒2修改了集合A的結構(是結構上面的修改,而不是簡單的修改集合元素的內容),那麼這個時候程式就會丟擲 ConcurrentModificationException 異常,從而產生fail-fast機制。
    • 原因:
      • 迭代器在遍歷時直接訪問集合中的內容,並且在遍歷過程中使用一個 modCount 變數。集合在被遍歷期間如果內容發生變化,就會改變modCount的值。每當迭代器使用hashNext()/next()遍歷下一個元素之前,都會檢測modCount變數是否為expectedmodCount值,是的話就返回遍歷;否則丟擲異常,終止遍歷。
    • 解決辦法:
      • 1.在遍歷過程中,所有涉及到改變modCount值得地方全部加上synchronized。
      • 2.使用CopyOnWriteArrayList來替換ArrayList

3.0.0.5 ArrayList,Vector和LinkList的區別,底層分別是怎麼實現的?儲存空間是如何擴容的?什麼是載入因子?

  • ArrayList
    • ArrayList的底層結構是陣列,可用索引實現快速查詢;是動態陣列,相比於陣列容量可實現動態增長。
    • ArrayList非執行緒安全,建議在單執行緒中才使用ArrayList,而在多執行緒中可以選擇Vector或者CopyOnWriteArrayList;預設初始容量為10,每次擴容為原來的1.5倍
  • Vector
    • 和ArrayList幾乎是一樣的,Vector使用了synchronized關鍵字,是執行緒安全的,比ArrayList開銷更大,訪問更慢;預設初始容量為10,預設每次擴容為原來的2倍,可通過capacityIncrement屬性設定
  • LinkList
    • LinkedList底層結構是連結串列,增刪速度快;是一個雙向迴圈連結串列,也可以被當作堆疊、佇列或雙端佇列

3.0.0.6 如何理解ArrayList的擴容消耗?Arrays.asList方法後的List可以擴容嗎?ArrayList如何序列化?

  • 如何理解ArrayList的擴容消耗
    • 由於ArrayList使用elementData = Arrays.copyOf(elementData, newCapacity);進行擴容,而每次都會重新建立一個newLength長度的陣列,所以擴容的空間複雜度為O(n),時間複雜度為O(n)
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } 
  • Arrays.asList方法後的List可以擴容嗎?
    • 不能,asList返回的List為只讀的。其原因為:asList方法返回的ArrayList是Arrays的一個內部類,並且沒有實現add,remove等操作
  • List怎麼實現排序?
    • 實現排序,可以使用自定義排序:list.sort(new Comparator(){...})
    • 或者使用Collections進行快速排序:Collections.sort(list)
  • ArrayList如何序列化?
    • ArrayList 基於陣列實現,並且具有動態擴容特性,因此儲存元素的陣列不一定都會被使用,那麼就沒必要全部進行序列化。
    • 技術部落格大總結
    • 儲存元素的陣列 elementData 使用 transient 修飾,該關鍵字宣告陣列預設不會被序列化。
    transient Object[] elementData; // non-private to simplify nested class access 
    • ArrayList 實現了 writeObject() 和 readObject() 來控制只序列化陣列中有元素填充那部分內容。
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;
        s.defaultReadObject();
        s.readInt(); // ignored
        if (size > 0) { ensureCapacityInternal(size); Object[] a = elementData; for (int i=0; i<size; i++) { a[i] = s.readObject(); } } } private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ int expectedModCount = modCount; s.defaultWriteObject(); s.writeInt(size); for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } 
    • 序列化時需要使用 ObjectOutputStream 的 writeObject() 將物件轉換為位元組流並輸出。而 writeObject() 方法在傳入的物件存在 writeObject() 的時候會去反射呼叫該物件的 writeObject() 來實現序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理類似。
    ArrayList list = new ArrayList();
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); oos.writeObject(list); 

3.0.0.7 如何理解list集合讀寫機制和讀寫效率?什麼是CopyOnWriteArrayList,它與ArrayList有何不同?

  • 讀寫機制
    • ArrayList在執行插入元素是超過當前陣列預定義的最大值時,陣列需要擴容,擴容過程需要呼叫底層System.arraycopy()方法進行大量的陣列複製操作;在刪除元素時並不會減少陣列的容量(如果需要縮小陣列容量,可以呼叫trimToSize()方法);在查詢元素時要遍歷陣列,對於非null的元素採取equals的方式尋找。
    • LinkedList在插入元素時,須建立一個新的Entry物件,並更新相應元素的前後元素的引用;在查詢元素時,需遍歷連結串列;在刪除元素時,要遍歷連結串列,找到要刪除的元素,然後從連結串列上將此元素刪除即可。
    • Vector與ArrayList僅在插入元素時容量擴充機制不一致。對於Vector,預設建立一個大小為10的Object陣列,並將capacityIncrement設定為0;當插入元素陣列大小不夠時,如果capacityIncrement大於0,則將Object陣列的大小擴大為現有size+capacityIncrement;如果capacityIncrement<=0,則將Object陣列的大小擴大為現有大小的2倍。
  • 讀寫效率
    • ArrayList對元素的增加和刪除都會引起陣列的記憶體分配空間動態發生變化。因此,對其進行插入和刪除速度較慢,但檢索速度很快。
    • LinkedList由於基於連結串列方式存放資料,增加和刪除元素的速度較快,但是檢索速度較慢。
  • 什麼是CopyOnWriteArrayList,它與ArrayList有何不同?
    • CopyOnWriteArrayList是ArrayList的一個執行緒安全的變體,其中所有可變操作(add、set等等)都是通過對底層陣列進行一次新的複製來實現的。相比較於ArrayList它的寫操作要慢一些,因為它需要例項的快照。
    • CopyOnWriteArrayList中寫操作需要大面積複製陣列,所以效能肯定很差,但是讀操作因為操作的物件和寫操作不是同一個物件,讀之間也不需要加鎖,讀和寫之間的同步處理只是在寫完後通過一個簡單的"="將引用指向新的陣列物件上來,這個幾乎不需要時間,這樣讀操作就很快很安全,適合在多執行緒裡使用,絕對不會發生ConcurrentModificationException ,因此CopyOnWriteArrayList適合使用在讀操作遠遠大於寫操作的場景裡,比如快取。
    • 技術部落格大總結

3.0.1.0 HashSet和TreeSet的區別?是如何保證唯一值的,底層怎麼做到的?

  • HashSet
    • 不能保證元素的排列順序;使用Hash演算法來儲存集合中的元素,有良好的存取和查詢效能;通過equal()判斷兩個元素是否相等,並兩個元素的hashCode()返回值也相等
  • TreeSet
    • 是SortedSet介面的實現類,根據元素實際值的大小進行排序;採用紅黑樹的資料結構來儲存集合元素;支援兩種排序方法:自然排序(預設情況)和定製排序。前者通過實現Comparable介面中的compareTo()比較兩個元素之間大小關係,然後按升序排列;後者通過實現Comparator介面中的compare()比較兩個元素之間大小關係,實現定製排列

3.0.1.5 HashMap和Hashtable的區別?HashMap在put、get元素的過程?體現了什麼資料結構?

  • HashMap
    • 基於AbstractMap類,實現了Map、Cloneable(能被克隆)、Serializable(支援序列化)介面; 非執行緒安全;允許存在一個為null的key和任意個為null的value;採用連結串列雜湊的資料結構,即陣列和連結串列的結合;初始容量為16,填充因子預設為0.75,擴容時是當前容量翻倍,即2capacity
  • Hashtable
    • 基於Map介面和Dictionary類;執行緒安全,開銷比HashMap大,如果多執行緒訪問一個Map物件,使用Hashtable更好;不允許使用null作為key和value;底層基於雜湊表結構;初始容量為11,填充因子預設為0.75,擴容時是容量翻倍+1,即2capacity+1
    • HashTable裡使用的是synchronized關鍵字,這其實是對物件加鎖,鎖住的都是物件整體,當Hashtable的大小增加到一定的時候,效能會急劇下降,因為迭代時需要被鎖定很長的時間。
  • HashMap在put、get元素的過程
    • 向Hashmap中put元素時,首先判斷key是否為空,為空則直接呼叫putForNullKey(),不為空則計算key的hash值得到該元素在陣列中的下標值;如果陣列在該位置處沒有元素,就直接儲存;如果有,還要比較是否存在相同的key,存在的話就覆蓋原來key的value,否則將該元素儲存在鏈頭,先儲存的在鏈尾。
    • 從Hashmap中get元素時,計算key的hash值找到在陣列中的對應的下標值,返回該key對應的value即可,如果有衝突就遍歷該位置連結串列尋找key相同的元素並返回對應的value
  • 體現了什麼資料結構
    • HashMap採用連結串列雜湊的資料結構,即陣列和連結串列的結合,在Java8後又結合了紅黑樹,當連結串列元素超過8個將連結串列轉換為紅黑樹
    • 技術部落格大總結

3.0.1.6 如何保證HashMap執行緒安全?底層怎麼實現的?HashMap是有序的嗎?如何實現有序?

  • 使用ConcurrentHashMap可保證執行緒安全
    • ConcurrentHashMap是執行緒安全的HashMap,它採取鎖分段技術,將資料分成一段一段的儲存,然後給每一段資料配一把鎖,當一個執行緒佔用鎖訪問其中一個段資料的時候,其他段的資料也能被其他執行緒訪問。在JDK1.8中對ConcurrentHashmap做了兩個改進:
      • 取消segments欄位,直接採用transient volatile HashEntry<K,V>[] table儲存資料,將陣列元素作為鎖,對每一行資料進行加鎖,可減少併發衝突的概率
      • 資料結構由“陣列+單向連結串列”變為“陣列+單向連結串列+紅黑樹”,使得查詢的時間複雜度可以降低到O(logN),改進一定的效能。
    • 通俗一點解釋:ConcurrentHashMap引入了分割(Segment),可以理解為把一個大的Map拆分成N個小的HashTable,在put方法中,會根據hash(paramK.hashCode())來決定具體存放進哪個Segment,如果檢視Segment的put操作,我們會發現內部使用的同步機制是基於lock操作的,這樣就可以對Map的一部分(Segment)進行上鎖,這樣影響的只是將要放入同一個Segment的元素的put操作,保證同步的時候,鎖住的不是整個Map(HashTable就是這麼做的),相對於HashTable提高了多執行緒環境下的效能,因此HashTable已經被淘汰了。技術部落格大總結
  • 使用LinkedHashMap可實現有序
    • HashMap是無序的,而LinkedHashMap是有序的HashMap,預設為插入順序,還可以是訪問順序,基本原理是其內部通過Entry維護了一個雙向連結串列,負責維護Map的迭代順序

3.0.1.7 HashMap儲存兩個物件的hashcode相同會發生什麼?如果兩個鍵的hashcode相同,你如何獲取值物件?

  • HashMap儲存兩個物件的hashcode相同會發生什麼?
    • 錯誤回答:因為hashcode相同,所以兩個物件是相等的,HashMap將會丟擲異常,或者不會儲存它們。
    • 正確回答:兩個物件就算hashcode相同,但是它們可能並不相等。如果不明白,可以先看看我的這篇部落格:Hash和HashCode深入理解。回答“因為hashcode相同,所以它們的bucket位置相同,‘碰撞’會發生。因為HashMap使用連結串列儲存物件,這個Entry(包含有鍵值對的Map.Entry物件)會儲存在連結串列中。
  • HashMap1.7和1.8的區別
    • 在JDK1.6,JDK1.7中,HashMap採用陣列+連結串列實現,即使用連結串列處理衝突,同一hash值的連結串列都儲存在一個連結串列裡。但是當位於一個連結串列中的元素較多,即hash值相等的元素較多時,通過key值依次查詢的效率較低。
    • JDK1.8中,HashMap採用位陣列+連結串列+紅黑樹實現,當連結串列長度超過閾值(8)時,將連結串列轉換為紅黑樹,這樣大大減少了查詢時間。
  • 如果兩個鍵的hashcode相同,你如何獲取值物件?
    • 當呼叫get()方法,HashMap會使用鍵物件的hashcode找到bucket位置,然後獲取值物件。當然如果有兩個值物件儲存在同一個bucket,將會遍歷連結串列直到找到值物件。
    • 在沒有值物件去比較,如何確定確定找到值物件的?因為HashMap在連結串列中儲存的是鍵值對,找到bucket位置之後,會呼叫keys.equals()方法去找到連結串列中正確的節點,最終找到要找的值物件。
    • 技術部落格大總結

3.0.1.8 HashMap為什麼不直接使用hashCode()處理後的雜湊值直接作為table的下標?

  • 不直接使用hashCode()處理後的雜湊值
    • hashCode()方法返回的是int整數型別,其範圍為-(231)~(231-1),約有40億個對映空間,而HashMap的容量範圍是在16(初始化預設值)~2 ^ 30,HashMap通常情況下是取不到最大值的,並且裝置上也難以提供這麼多的儲存空間,從而導致通過hashCode()計算出的雜湊值可能不在陣列大小範圍內,進而無法匹配儲存位置;
  • HashMap是使用了哪些方法來有效解決雜湊衝突的
    • 1.使用鏈地址法(使用散列表)來連結擁有相同hash值的資料;
    • 2.使用2次擾動函式(hash函式)來降低雜湊衝突的概率,使得資料分佈更平均;
    • 3.引入紅黑樹進一步降低遍歷的時間複雜度,使得遍歷更快;
  • 如何解決匹配儲存位置問題
    • HashMap自己實現了自己的hash()方法,通過兩次擾動使得它自己的雜湊值高低位自行進行異或運算,降低雜湊碰撞概率也使得資料分佈更平均;
    • 在保證陣列長度為2的冪次方的時候,使用hash()運算之後的值與運算(&)(陣列長度 - 1)來獲取陣列下標的方式進行儲存,這樣一來是比取餘操作更加有效率,二來也是因為只有當陣列長度為2的冪次方時,h&(length-1)才等價於h%length,三來解決了“雜湊值與陣列大小範圍不匹配”的問題;
  • 為什麼陣列長度要保證為2的冪次方呢?
    • 只有當陣列長度為2的冪次方時,h&(length-1)才等價於h%length,即實現了key的定位,2的冪次方也可以減少衝突次數,提高HashMap的查詢效率;
    • 技術部落格大總結
    • 如果 length 為 2 的次冪 則 length-1 轉化為二進位制必定是 11111……的形式,在於 h 的二進位制與操作效率會非常的快,而且空間不浪費;如果 length 不是 2 的次冪,比如 length 為 15,則 length - 1 為 14,對應的二進位制為 1110,在於 h 與操作,最後一位都為 0 ,而 0001,0011,0101,1001,1011,0111,1101 這幾個位置永遠都不能存放元素了,空間浪費相當大,更糟的是這種情況中,陣列可以使用的位置比陣列長度小了很多,這意味著進一步增加了碰撞的機率,減慢了查詢的效率!這樣就會造成空間的浪費。

3.0.1.9 為什麼HashMap中String、Integer這樣的包裝類適合作為K?為啥不用其他作key值?

  • 為什麼HashMap中String、Integer這樣的包裝類適合作為K?
    • String、Integer等包裝類的特效能夠保證Hash值的不可更改性和計算準確性,能夠有效的減少Hash碰撞的機率
      • 都是final型別,即不可變性,保證key的不可更改性,不會存在獲取hash值不同的情況
      • 內部已重寫了equals()、hashCode()等方法,遵守了HashMap內部的規範(不清楚可以去上面看看putValue的過程),不容易出現Hash值計算錯誤的情況;
  • 想要讓自己的Object作為K應該怎麼辦呢?
    • 重寫hashCode()和equals()方法
      • 重寫hashCode()是因為需要計算儲存資料的儲存位置,需要注意不要試圖從雜湊碼計算中排除掉一個物件的關鍵部分來提高效能,這樣雖然能更快但可能會導致更多的Hash碰撞;
      • 重寫equals()方法,需要遵守自反性、對稱性、傳遞性、一致性以及對於任何非null的引用值x,x.equals(null)必須返回false的這幾個特性,目的是為了保證key在雜湊表中的唯一性;
  • 總結
    • 採用合適的equals()和hashCode()方法的話,將會減少碰撞的發生,提高效率。不可變性使得能夠快取不同鍵的hashcode,這將提高整個獲取物件的速度,使用String,Interger這樣的wrapper類作為鍵是非常好的選擇。
    • 技術部落格大總結

3.0.2.0 HashMap是如何擴容的?如何理解HashMap的大小超過了負載因子定義的容量?重新調整HashMap大小存在什麼問題嗎?

  • HashMap是為啥要擴容
    • 當連結串列陣列的容量超過初始容量*載入因子(預設0.75)時,再雜湊將連結串列陣列擴大2倍,把原連結串列陣列的搬移到新的陣列中。為什麼需要使用載入因子?為什麼需要擴容呢?因為如果填充比很大,說明利用的空間很多,如果一直不進行擴容的話,連結串列就會越來越長,這樣查詢的效率很低,擴容之後,將原來連結串列陣列的每一個連結串列分成奇偶兩個子連結串列分別掛在新連結串列陣列的雜湊位置,這樣就減少了每個連結串列的長度,增加查詢效率。
  • 如何理解HashMap的大小超過了負載因子(load factor)定義的容量?
    • 預設的負載因子大小為0.75,也就是說,當一個map填滿了75%的bucket時候,和其它集合類(如ArrayList等)一樣,將會建立原來HashMap大小的兩倍的bucket陣列,來重新調整map的大小,並將原來的物件放入新的bucket陣列中。這個過程叫作rehashing,因為它呼叫hash方法找到新的bucket位置。
  • 重新調整HashMap大小存在什麼問題嗎?技術部落格大總結
    • 當多執行緒的情況下,可能產生條件競爭。當重新調整HashMap大小的時候,確實存在條件競爭,因為如果兩個執行緒都發現HashMap需要重新調整大小了,它們會同時試著調整大小。在調整大小的過程中,儲存在連結串列中的元素的次序會反過來,因為移動到新的bucket位置的時候,HashMap並不會將元素放在連結串列的尾部,而是放在頭部,這是為了避免尾部遍歷(tail traversing)。如果條件競爭發生了,那麼就死迴圈了。

其他介紹

01.關於部落格彙總連結

02.關於我的部落格