欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

散列算法與散列碼(實例講解)

 更新時間:2017年08月24日 08:55:19   投稿:jingxian  
下面小編就為大家?guī)硪黄⒘兴惴ㄅc散列碼(實例講解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

一、引入

/**
 * Description:新建一個類作為map的key
 */
public class Groundhog
{
  protected int number;

  public Groundhog(){
  }
  public Groundhog(int number)
  {
    this.number = number;
  }

  @Override
  public String toString()
  {
    return "Groundhog{" + "number=" + number + '}';
  }
}

/**
 * Description:新建一個類作為map的value
 */
public class Prediction
{
  private boolean shadow=Math.random() > 0.5;

  @Override
  public String toString()
  {
    if (shadow) return "Six more weeks of Winter";
    else return "Early Spring!";
  }
}

/**
 * Description:測試類
 */
public class SpringDetector
{
  public static void detectSpring(Class grondHotClass) throws Exception{
    Constructor constructor = grondHotClass.getConstructor(new Class[]{int.class});
    Map map=new HashMap();
    for (int i=0;i<10;i++){
      map.put(constructor.newInstance(new Object[]{new Integer(i)}),new Prediction());
    }
    System.out.println("map="+map);

    Groundhog groundhog=(Groundhog)constructor.newInstance(new Object[]{new Integer(3)});
    System.out.println(groundhog);

    if (map.containsKey(groundhog)) {//查找這個key是否存在
      System.out.println((Prediction)map.get(groundhog));
    }else {
      System.out.println("key not find:"+groundhog);
    }

  }
  public static void main(String[] args)
  {
    try {
      detectSpring(Groundhog.class);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

看這個結(jié)果,問題就來了,map中明明存在Groudhog{number=3},為什么結(jié)果顯示的是Key not find呢??問題出在哪里呢?原來是Groudhog類沒有重寫hashCode()方法,所以這里是使用Object的hashCode()方法生成散列碼,而他默認是使用對象的地址計算散列碼。因此,由Groudhog(3)生成的第一個實例的散列碼與Groudhog(3)生成的散列碼是不同的,所以無法查找到 key。但是僅僅重寫hashCode()還是不夠的,除非你重寫equals()方法。原因在于不同的對象可能計算出同樣的hashCode的值,hashCode 的值并不是唯一的,當(dāng)hashCode的值一樣時,就會使用equals()判斷當(dāng)前的“鍵”是否與表中的存在的鍵“相同”,即“

如果兩個對象相同,那么他們的hashCode值一定相同。
如果兩個對象的hashCode值相同,他們不一定相同。
正確的equals()方法必須滿足下列5個條件:
1、自反性: x.equals(x) 一定成立。
2、對稱性: 如果x.equals(y)成立,那么y.equals(x)也一定成立。
3、傳遞性:如果x.equals(y)=true ,y.equals(z)=true,那么x.equals(z)=true也成立。
4、一致性:無論調(diào)用x.equal(y)多少次,返回的結(jié)果應(yīng)該保持一致。
5、對任何不是null的x,x.equals(null)一定返回false。

二、理解hashCode()

散列的價值在于速度:散列使得查詢得以快速執(zhí)行。由于速度的瓶頸是對“鍵”進行查詢,而存儲一組元素最快的數(shù)據(jù)結(jié)構(gòu)是數(shù)組,所以用它來代表鍵的信息,注意:數(shù)組并不保存“鍵”的本身。而通過“鍵”對象生成一個數(shù)字,將其作為數(shù)組的下標索引。這個數(shù)字就是散列碼,由定義在Object的hashCode()生成(或成為散列函數(shù))。同時,為了解決數(shù)組容量被固定的問題,不同的“鍵”可以產(chǎn)生相同的下標。那對于數(shù)組來說?怎么在同一個下標索引保存多個值呢??原來數(shù)組并不直接保存“值”,而是保存“值”的 List。然后對 List中的“值”使用equals()方法進行線性的查詢。這部分的查詢自然會比較慢,但是如果有好的散列函數(shù),每個下標索引只保存少量的值,只對很少的元素進行比較,就會快的多。

不知道大家有沒有理解我上面在說什么。不過沒關(guān)系,下面會有一個例子幫助大家理解。不過我之前一直被一個問題糾結(jié):為什么一個hashCode的下標存的會有多個值?因為hashMap里面只能有唯一的key啊,所以只能有唯一的value在那個下標才對啊。這里就要提出一個新的概念哈希沖突的問題,借用網(wǎng)上的一個例子:

比如:數(shù)組的長度是5。這時有一個數(shù)據(jù)是6。那么如何把這個6存放到長度只有5的數(shù)組中呢。按照取模法,計算6%5,結(jié)果是1,那么就把6放到數(shù)組下標是1的位置。那么,7就應(yīng)該放到2這個位置。到此位置,哈希沖突還沒有出現(xiàn)。這時,有個數(shù)據(jù)是11,按照取模法,11%5=1,也等于1。那么原來數(shù)組下標是1的地方已經(jīng)有數(shù)了,是6。這時又計算出1這個位置,那么數(shù)組1這個位置,就必須儲存兩個數(shù)了。這時,就叫哈希沖突。沖突之后就要按照順序來存放了。所以這里Java中用的解決方法就是在這個hashCode上存一個List,當(dāng)遇到相同的hashCode時,就往這個List里add元素就可以了。這才是hash原理的精髓所在??!哈哈、糾結(jié)我一天。

三、HashMap的性能因子

容量(Capacity):散列表中的數(shù)量。

初始化容量(Initial capacity):創(chuàng)建散列表時桶的數(shù)量。HashMap 和 HashSet都允許你在構(gòu)造器中制定初始化容量。

尺寸(Size):當(dāng)前散列表中記錄的數(shù)量。

負載因子(Load factor):等于"size/capacity"。負載因子為0,表示空的散列表,0.5表示半滿的散列表,依次類推。輕負載的散列表具有沖突少、適宜插入與適宜查詢的特點(但是使用迭代器遍歷會變慢)。HashMap和hashSet的構(gòu)造器允許你制定負載因子。這意味著,當(dāng)負載達到制定值時,容器會自動成倍的增加容量,并將原有的對象重新分配,存入新的容器內(nèi)(這稱為“重散列”rehashing)。HashMap默認的負載因子為0.75,這很好的權(quán)衡了時間和空間的成本。

備注:為使散列分布均衡,Java的散列函數(shù)都使用2的整數(shù)次方來作為散列表的理想容量。對現(xiàn)代的處理器來說,除法和求余是最慢的動作。使用2的整數(shù)次方的散列表,可用掩碼代替除法。因為get()是使用最多的操作,求余數(shù)的%操作是其開銷的大部分,而使用2的整數(shù)次方可以消除此開銷(也可能對hashCode()有些影響)

四、怎么重寫hashCode()

現(xiàn)在的IDE工具中,一般都能自動的幫我們重寫了hashCode()和equals()方法,但那或許并不是最優(yōu)的,重寫hashCode()有兩個原則:

必須速度快,并且必須有意義。也就是說,它必須基于對象的內(nèi)容生成散列碼。

應(yīng)該產(chǎn)生分布均勻的散列碼。如果散列碼都集中在一塊,那么在某些區(qū)域的負載就會變得很重。

下面是怎么寫出一份像樣的hashCode()的基本指導(dǎo):

1、給int變量result 賦予某個非零值常量,例如 17。

2、為每個對象內(nèi)每個有意義的屬性f (即每個可以做equals()的屬性)計算出一個 int 散列碼c:

3、合并計算得到的散列值:result=37*result+c;

4、返回 result;

5、檢查hashCode()最后生成的結(jié)果,確保相同的對象有相同的散列碼。

五、自定義HashMap

下面我們將自己寫一個hashMap,便于了解底層的原理,大家如果看的懂下面的代碼,也就很好的理解了hashCode的原理了。

/**
 * Description:首先新建一個類作為map中存儲的對象并重寫了hashCode()和equals()方法
 */
public class MPair implements Map.Entry,Comparable
{
  private Object key,value;

  public MPair(Object key,Object value)
  {
    this.key = key;
    this.value=value;
  }
  @Override
  public int compareTo(Object o)
  {
    return ((Comparable)key).compareTo(((MPair)o).key);
  }
  
  @Override
  public Object getKey()
  {
    return key;
  }

  @Override
  public Object getValue()
  {
    return value;
  }

   @Override
  public int hashCode()
  {
    int result = key != null ? key.hashCode() : 0;
    result = 31 * result + (value != null ? value.hashCode() : 0);
    return result;
  }

  @Override
  public boolean equals(Object o)
  {
    return key.equals(((MPair)o).key);
  }

  @Override
  public Object setValue(Object v)
  {
    Object result=value;
    this.value=v;
    return result;
  }
  
  @Override
  public String toString()
  {
    return "MPair{" + "key=" + key + ", value=" + value + '}';
  }
public class SimpleHashMap extends AbstractMap
{
  
  private static final int SZ=3;//定一個初始大小的哈希表容量
  private LinkedList[] linkedLists=new LinkedList[SZ];//建一個hash數(shù)組,用linkedList實現(xiàn)
  public Object put(Object key,Object value){
    Object result=null;
    int index=key.hashCode() % SZ;//對key的值做求模法求出index
    if (index<0) index=-index;
    if (linkedLists[index]==null) linkedLists[index]=new LinkedList();//如果這個index位置沒有對象,就新建一個

    LinkedList linkedList = linkedLists[index];//取出這個index的對象linkedList
    MPair mPair = new MPair(key,value);//新建要存儲的對象mPair
    ListIterator listIterator = linkedList.listIterator();
    boolean found =false;
    while (listIterator.hasNext()){//遍歷這個index位置的List,如果查找到跟之前一樣的對象(根據(jù)equals來比較),則更新那個key對應(yīng)的value
      Object next = listIterator.next();
      if (next.equals(mPair)){
        result = ((MPair) next).getValue();
        listIterator.set(mPair);//更新動作
        found=true;
        break;
      }
    }
    if (!found) linkedLists[index].add(mPair);//如果沒有找到這個對象,則在這index的List對象上新增一個元素。
    return result;

  }

  public Object get(Object key){
    int index = key.hashCode() % SZ;
    if (index<0) index=-index;
    if (linkedLists[index]==null) return null;
    LinkedList linkedList = linkedLists[index];
    MPair mPair=new MPair(key,null);//新建一個空的對象值,因為equals()的比較是看他們的key是否相等,而在List中的遍歷對象的時候,是通過key來查找對象的。
    ListIterator listIterator = linkedList.listIterator();
    while (listIterator.hasNext()){
      Object next = listIterator.next();
      if (next.equals(mPair)) return ((MPair)next).getValue();//找到了這個key就返回這個value
    }
    return null;

  }

  @Override
  public Set<Entry> entrySet()
  {
    Set set=new HashSet();
    for (int i=0;i<linkedLists.length;i++){
      if (linkedLists[i]==null) continue;
      Iterator iterator = linkedLists[i].iterator();
      while (iterator.hasNext()){
        set.add(iterator.next());
      }
    }
    return set;
  }

  public static void main(String[] args)
  {
    SimpleHashMap simpleHashMap=new SimpleHashMap();
    simpleHashMap.put("1", "1");
    simpleHashMap.put("2", "2");
    simpleHashMap.put("3","3");
    simpleHashMap.put("4","4");//這里有四個元素,其中key是1和key是4的index是一樣的,所以index為1的List上面存了兩個元素。
    System.out.println(simpleHashMap);
    Object o = simpleHashMap.get("1");
    System.out.println(o);
    Object o1 = simpleHashMap.get("4");
    System.out.println(o1);

  }
}

六、結(jié)語

不知道大家理解了沒?整了我一天,終于還算大概理解了其中的原理了。文筆比較粗糙,大家湊活看吧,畢竟,不會做飯的作家不是好程序員?。」?..... 或者,可能我有很多理解的不到位的地方,還請大家不吝指教!

相關(guān)文章

  • 在SpringBoot3中spring.factories配置不起作用的原因和解決方法

    在SpringBoot3中spring.factories配置不起作用的原因和解決方法

    本文給大家介紹了在SpringBoot3中spring.factories配置的自動裝配不生效的原因和解決方法,文中通過代碼和圖文給出了詳細的解決方法,具有一定的參考價值,需要的朋友可以參考下
    2024-02-02
  • 解決IDEA2020控制臺亂碼的方法

    解決IDEA2020控制臺亂碼的方法

    這篇文章主要介紹了解決IDEA2020控制臺亂碼的方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • 帶你詳細了解Spring Security的注解方式開發(fā)

    帶你詳細了解Spring Security的注解方式開發(fā)

    這篇文章主要介紹了詳解spring security四種實現(xiàn)方式,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-08-08
  • SpringCloud之分布式配置中心Spring Cloud Config高可用配置實例代碼

    SpringCloud之分布式配置中心Spring Cloud Config高可用配置實例代碼

    這篇文章主要介紹了SpringCloud之分布式配置中心Spring Cloud Config高可用配置實例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04
  • java ssm框架實現(xiàn)分頁功能的示例代碼(oracle)

    java ssm框架實現(xiàn)分頁功能的示例代碼(oracle)

    這篇文章主要介紹了java ssm框架實現(xiàn)分頁功能的示例代碼(oracle),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • 一篇文章帶你了解SpringBoot Web開發(fā)

    一篇文章帶你了解SpringBoot Web開發(fā)

    這篇文章主要介紹了使用Spring Boot搭建Java web項目及開發(fā)過程,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-08-08
  • Java每隔兩個數(shù)刪掉一個數(shù)問題詳解

    Java每隔兩個數(shù)刪掉一個數(shù)問題詳解

    這篇文章主要介紹了Java每隔兩個數(shù)刪掉一個數(shù)問題詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Spring @Transactional注解的聲明式事務(wù)簡化業(yè)務(wù)邏輯中的事務(wù)管理

    Spring @Transactional注解的聲明式事務(wù)簡化業(yè)務(wù)邏輯中的事務(wù)管理

    這篇文章主要為大家介紹了Spring @Transactional注解的聲明式事務(wù)簡化業(yè)務(wù)邏輯中的事務(wù)管理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-10-10
  • JAVA獲得域名IP地址的方法

    JAVA獲得域名IP地址的方法

    這篇文章主要介紹了JAVA獲得域名IP地址的方法,涉及java域名操作的相關(guān)技巧,需要的朋友可以參考下
    2015-06-06
  • Java實現(xiàn)二叉查找樹的增刪查詳解

    Java實現(xiàn)二叉查找樹的增刪查詳解

    二叉查找樹(ADT)是一個具有對于樹種的某個節(jié)點X,它的左節(jié)點都比X小,它的右節(jié)點都比X大的二叉樹。本文將用Java實現(xiàn)二叉查找樹的增刪查,需要的可以參考一下
    2022-06-06

最新評論