源碼衝浪之HashMap

HashMap是咱們最經常使用到的集合之一,是java很是典型的數據結構。學習它的源碼是很是隻有必要的,咱們所要了解的並不單單是「HashMap不是線程安全的,HashTable是線程安全的,經過synchronized實現的。HashMap取值很是快」等等。java

瞭解hashmap必需要先對hashmap的存儲結構有個瞭解
複製代碼

它是屬於數組及鏈表相結合的存儲結構。如上圖 x軸爲數組,y軸爲鏈表。 數組存儲方式在內存地址是連續大小固定,一旦分配沒法被其餘引用佔用,查詢迅速,時間複雜度O(1),插入刪除比較慢,時間複雜度爲O(n)。 而鏈表存儲方式則與數組相反,屬於非連續性,大小非固定,插入及刪除塊,查詢速度慢。 因此HashMap相對中庸。算法

1 HashMap的數據結構是啥?數據結構上存儲的數據對象結構是啥? HashMap是一個存儲數據對象<封裝了K,V屬性的對象>的集合,而這個集合是數組+鏈表類型的數據結構。編程

2 根據源碼來分析hashMap內部的精髓 hash算法如何保證散列均勻衝突的解決方式數組

談到hash 一般咱們jdk的equals在比較的時候就會使用hash算法,此算法會定位到對象的存儲位置 具體hash的原理是: hash函數:找到存儲過程 被重寫的hashCode(key)安全

index=h=Hash(int hashCode)bash

(key.hashCode)&&length -1數據結構

length 2^n 經過h就能夠找到數組下標的位置ide

例子以下: 2^4=16 length-1 =15 二進制爲 01111 h返回的是 10101 數組上存儲的位置爲: 00101 【上下都是1纔是1】函數

好處: 1 散列的範圍被低位限制---》散列位置必定在咱們的索引範圍(即length-1)以內。 2 低位的0若是越多 表明咱們散列的結果越固定。【想象一個如果非length-1就會發生 10000 低位0較多,致使散列結果幾乎就是一致】,致使衝突越多,致使數組位置的利用率不高。學習

3 手寫一個本身的hashmap集合

首先咱們會寫一個本身的接口 面向接口編程【接口內部只保留最基礎的put及get方法,而後定義內部接口】

而後就是寫自定義的hashMap來繼承此接口

定義參數而且補充了Spring門面模式的構造【便可以傳參,若是不傳參的話調用此類上面定義好的參數】

書寫put方法

其中注意的是擴容的方法及原理

獲取數組下標的方法而且重寫hash算法

定義內部類,重寫·Entry類

而後就是寫get方法

代碼清單以下:

MyMap接口

package com.epoint.HashMap;



/**

 * ,面向接口編程

 * 

 * @author lulf

 *

 * @param <K>

 * @param <V>

 */

public interface MyMap<K, V> {

 // MyMap 基本功能是快速存

 public V put(K k, V v);



 // 快速取

 public V get(K k);



 // 定義一個內部的接口

 public interface Entry<K, V> {



  public K getKey();



  public V getValue();

 }



}`

myhashMap
`package com.epoint.HashMap;



import java.util.ArrayList;

import java.util.List;



public class MyHashMap<K, V> implements MyMap<K, V> {



 // 定義數組大小 16

 // 結合着下面的擴容因子來解釋一波:假如數組用了 4 usesize/defaulLenth =4/16=0.25 即便用率<0.75,不會擴容

 private static int defaulLenth = 1 << 4;



 // 擴容標準 所使用的useSize / 數組長度 >0.75

 // defaulAddSizeFactor 過大 形成擴容機率變低 存儲小 可是就是存與取的效率下降

 // 0.9 有限的數組長度空間位置內會造成鏈表 在存與取值中都必須進行大量的遍歷和判斷(邏輯)

 // 太小 內存使用比較多,使用率不高,形成浪費

 private static double defaulAddSizeFactor = 0.75;



 // 使用數組位置的總數

 private int useSize;



 // 定義Map 骨架 只要 數組之一 數組

 private Entry<K, V>[] table = null;



 // Spring 門面模式運用

 public MyHashMap() {

  this(defaulLenth, defaulAddSizeFactor);

 }



 public MyHashMap(int length, double defaulAddSizeFactor) {

  if (length < 0)

   throw new IllegalArgumentException("參數不能爲負數" + length);

  if (defaulAddSizeFactor <= 0 || Double.isNaN(defaulAddSizeFactor)) {

   throw new IllegalArgumentException("擴容標準必須是大於0的數字" + defaulAddSizeFactor);

  }

  this.defaulLenth = length;

  this.defaulAddSizeFactor = defaulAddSizeFactor;

  table = new Entry[defaulLenth];

 }



 @Override

 public V put(K k, V v) {

  // 存儲是判斷是否須要擴容

  if (useSize > defaulAddSizeFactor * defaulLenth) {

   up2Size();

  }

  // 獲取數組下標

  int index = getIndex(k, table.length);

  Entry<K, V> entry = table[index];

  // 判斷這個entry是否爲空,爲空意味着未被散列到

  if (entry == null) {

   table[index] = new Entry(k, v, null);

   useSize++;

  } else if (entry != null) {

   // 造成了鏈表結構

   table[index] = new Entry(k, v, entry);

  }

  return table[index].getValue();

 }



 // 尋找數組的下標

 private int getIndex(K k, int length) {

  int m = length - 1;

  int index = hash(k.hashCode()) & m;

  return index;

 }



 // 自定義寫本身的hash算法

 private int hash(int hashCode) {

  hashCode = hashCode ^ ((hashCode >>> 20) ^ (hashCode >>> 12));

  return hashCode ^ ((hashCode >>> 7) ^ (hashCode >>> 4));

 }



 // 擴容

 private void up2Size() {

  // 如何擴容,無非就是新建一個2倍空間的數組

  Entry<K, V>[] newTable = new Entry[2 * defaulLenth];

  // 老數組的內容拿到新數組中

  againHash(newTable);



 }



 // 將老數組內容散列到新數組中

 private void againHash(MyHashMap<K, V>.Entry<K, V>[] newTable) {

  List<Entry<K, V>> entryList = new ArrayList<MyHashMap<K, V>.Entry<K, V>>();

  // for循環 即老數組內容被所有遍歷到了entryList中

  for (int i = 0; i < table.length; i++) {

   if (table[i] == null) {

    continue;

   }

   // 繼續找存到數組上的entry對象

   foundEntryByNext(table[i], entryList);

  }

  // 設置entryList

  if (entryList.size() > 0) {

   useSize = 0;

   defaulLenth = 2 * defaulLenth;

   for (Entry<K, V> entry : entryList) {

    if (entry.next != null) {

     entry.next = null;

    }

    put(entry.getKey(), entry.getValue());

   }

  }



 }



 private void foundEntryByNext(MyHashMap<K, V>.Entry<K, V> entry, List<MyHashMap<K, V>.Entry<K, V>> entryList) {

  // 造成了鏈表結構

  if (entry != null && entry.next != null) {

   entryList.add(entry);

   // 遞歸,不斷地一層層取存entry

   foundEntryByNext(entry.next, entryList);

  } else {

   // 沒有鏈表的狀況

   entryList.add(entry);

  }



 }



 public int getUseSize() {

  return useSize;

 }



 @Override

 public V get(K k) {

  // hashCode (new Person(10,'llf'))--->hash---getindex--->最終位置

  int index = getIndex(k, table.length);

  if (table[index] == null) {

   throw new NullPointerException();

  }

  return findByValueByEqualKey(k, table[index]);

 }



 private V findByValueByEqualKey(K k, MyHashMap<K, V>.Entry<K, V> entry) {

  if (k == entry.getKey() || k.equals(entry.getKey())) {

   return entry.getValue();

  } else if (entry.next != null) {

   return findByValueByEqualKey(k, entry.next);

  }

  return null;

 }



 // 建立一個內部存儲的對象類型

 class Entry<K, V> implements MyMap.Entry<K, V> {

  K k;

  V v;

  // 指向那被this擠壓轄區的Entry對象

  Entry<K, V> next;



  public Entry(K k, V v, Entry<K, V> next) {

   this.k = k;

   this.v = v;

   this.next = next;

  }



  @Override

  public K getKey() {

   return k;

  }



  @Override

  public V getValue() {

   return v;

  }

 }
 
}
複製代碼
相關文章
相關標籤/搜索