1. 程式人生 > >java集合框架之HashCode

java集合框架之HashCode

封裝 app stringbu result ati des tor 平均值 http

參考http://how2j.cn/k/collection/collection-hashcode/371.html

List查找的低效率

假設在List中存放著無重復名稱,沒有順序的2000000個Hero
要把名字叫做“hero 1000000”的對象找出來
List的做法是對每一個進行挨個遍歷,直到找到名字叫做“hero 1000000”的英雄。
最差的情況下,需要遍歷和比較2000000次,才能找到對應的英雄。
測試邏輯:
1. 初始化2000000個對象到ArrayList中
2. 打亂容器中的數據順序
3. 進行10次查詢,統計每一次消耗的時間
不同計算機的配置情況下,所花的時間是有區別的。 在本機上,花掉的時間大概是600毫秒左右

技術分享圖片

package collection;
     
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
     
import charactor.Hero;
     
public class TestCollection { public static void main(String[] args) { List<Hero> heros = new ArrayList<Hero>(); for (int j = 0; j < 2000000; j++) { Hero h = new Hero("Hero " + j); heros.add(h); } // 進行10次查找,觀察大體的平均值
for (int i = 0; i < 10; i++) { // 打亂heros中元素的順序 Collections.shuffle(heros); long start = System.currentTimeMillis(); String target = "Hero 1000000"; for (Hero hero : heros) { if (hero.name.equals(target)) { System.
out.println("找到了 hero!" ); break; } } long end = System.currentTimeMillis(); long elapsed = end - start; System.out.println("一共花了:" + elapsed + " 毫秒"); } } }

HashMap的性能表現

使用HashMap 做同樣的查找
1. 初始化2000000個對象到HashMap中。
2. 進行10次查詢
3. 統計每一次的查詢消耗的時間
可以觀察到,幾乎不花時間,花費的時間在1毫秒以內

技術分享圖片

package collection;
  
import java.util.HashMap;
  
import charactor.Hero;
  
public class TestCollection {
    public static void main(String[] args) {
          
        HashMap<String,Hero> heroMap = new HashMap<String,Hero>();
        for (int j = 0; j < 2000000; j++) {
            Hero h = new Hero("Hero " + j);
            heroMap.put(h.name, h);
        }
        System.out.println("數據準備完成");
  
        for (int i = 0; i < 10; i++) {
            long start = System.currentTimeMillis();
              
            //查找名字是Hero 1000000的對象
            Hero target = heroMap.get("Hero 1000000");
            System.out.println("找到了 hero!" + target.name);
              
            long end = System.currentTimeMillis();
            long elapsed = end - start;
            System.out.println("一共花了:" + elapsed + " 毫秒");
        }
  
    }
}

HashMap原理與字典

在展開HashMap原理的講解之前,首先回憶一下大家初中和高中使用的漢英字典。

比如要找一個單詞對應的中文意思,假設單詞是Lengendary,首先在目錄找到Lengendary在第 555頁。

然後,翻到第555頁,這頁不只一個單詞,但是量已經很少了,逐一比較,很快就定位目標單詞Lengendary。

555相當於就是Lengendary對應的hashcode

分析HashMap性能卓越的原因

分析HashMap性能卓越的原因

-----hashcode概念-----
所有的對象,都有一個對應的hashcode(散列值)
比如字符串“gareen”對應的是1001 (實際上不是,這裏是方便理解,假設的值)
比如字符串“temoo”對應的是1004
比如字符串“db”對應的是1008
比如字符串“annie”對應的是1008

-----保存數據-----
準備一個數組,其長度是2000,並且設定特殊的hashcode算法,使得所有字符串對應的hashcode,都會落在0-1999之間
要存放名字是"gareen"的英雄,就把該英雄和名稱組成一個鍵值對,存放在數組的1001這個位置上
要存放名字是"temoo"的英雄,就把該英雄存放在數組的1004這個位置上
要存放名字是"db"的英雄,就把該英雄存放在數組的1008這個位置上
要存放名字是"annie"的英雄,然而 "annie"的hashcode 1008對應的位置已經有db英雄了,那麽就在這裏創建一個鏈表,接在db英雄後面存放annie

-----查找數據-----
比如要查找gareen,首先計算"gareen"的hashcode是1001,根據1001這個下標,到數組中進行定位,(根據數組下標進行定位,是非常快速的) 發現1001這個位置就只有一個英雄,那麽該英雄就是gareen.
比如要查找annie,首先計算"annie"的hashcode是1008,根據1008這個下標,到數組中進行定位,發現1008這個位置有兩個英雄,那麽就對兩個英雄的名字進行逐一比較(equals),因為此時需要比較的量就已經少很多了,很快也就可以找出目標英雄
這就是使用hashmap進行查詢,非常快原理。

這是一種用空間換時間的思維方式

技術分享圖片

HashSet判斷是否重復

HashSet的數據是不能重復的,相同數據不能保存在一起,到底如何判斷是否是重復的呢?
根據HashSet和HashMap的關系,我們了解到因為HashSet沒有自身的實現,而是裏面封裝了一個HashMap,所以本質上就是判斷HashMap的key是否重復。

再通過上一步的學習,key是否重復,是由兩個步驟判斷的:
hashcode是否一樣
如果hashcode不一樣,就是在不同的坑裏,一定是不重復的
如果hashcode一樣,就是在同一個坑裏,還需要進行equals比較
如果equals一樣,則是重復數據
如果equals不一樣,則是不同數據。

自定義字符串的hashcode

如下是Java API提供的String的hashcode生成辦法;

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] s[0] 表示第一位字符

n表示字符串的長度
本練習並不是要求去理解這個算法,而是自定義一個簡單的hashcode算法,計算任意字符串的hashcode
因為String類不能被重寫,所以我們通過一個靜態方法來返回一個String的hashcode

public static int hashcode(String)

如果字符串長度是0,則返回0。
否則: 獲取每一位字符,轉換成數字後,相加,最後乘以23

(s[0]+ s[1] + s[2] + s[3]+ s[n-1])*23.

如果值超過了1999,則取2000的余數,保證落在0-1999之間。
如果是負數,則取絕對值。
隨機生成長度是2-10的不等的100個字符串,打印用本hashcode獲取的值分別是多少

HashCode代碼

package Test.testtest;

/**
 * @Auther: 李景然
 * @Date: 2018/5/24 22:48
 * @Description:
 */
public class HashCode {
    public static void main(String[] args) {
        int i=0;
        while (i<100){
            double strLength=Math.ceil(Math.random()*8+2);
            StringBuilder sb=new StringBuilder();
            char c;
            int start=(int)0;
            int end=(int)z+1;

            for (int j=0;j<strLength;j++){
                c=(char) (Math.random()*(end-start)+start);
                if(Character.isLetterOrDigit(c)){
                    sb.append(c);
                }else{
                    j--;
                }

            }
            System.out.println(sb.toString()+"---"+hashcode(sb.toString()));
            i++;
        }
    }

    public static int hashcode(String str){
        int result=0;
        if(str==null||str.length()==0){
            return 0;
        }
        char[] chars=str.toCharArray();
        for (char c :chars){
            result+=(int)c;
        }
        result*=23;
        if(result>1999){
            result%=2000;
        }
        return result;
    }
}

部分結果:

技術分享圖片

自定義MyHashMap

根據前面學習的hashcode的原理和自定義hashcode, 設計一個MyHashMap,實現接口IHashMap

MyHashMap內部由一個長度是2000的對象數組實現。

設計put(String key,Object value)方法
首先通過上一個自定義字符串的hashcode練習獲取到該字符串的hashcode,然後把這個hashcode作為下標,定位到數組的指定位置。
如果該位置沒有數據,則把字符串和對象組合成鍵值對Entry,再創建一個LinkedList,把鍵值對,放進LinkedList中,最後把LinkedList 保存在這個位置。
如果該位置有數據,一定是一個LinkedList,則把字符串和對象組合成鍵值對Entry,插入到LinkedList後面。

設計 Object get(String key) 方法
首先通過上一個自定義字符串的hashcode練習獲取到該字符串的hashcode,然後把這個hashcode作為下標,定位到數組的指定位置。
如果這個位置沒有數據,則返回空
如果這個位置有數據,則挨個比較其中鍵值對的鍵-字符串,是否equals,找到匹配的,把鍵值對的值,返回出去。找不到匹配的,就返回空

IhashMap代碼

package Test.testtest;

/**
 * @Auther: 李景然
 * @Date: 2018/5/24 22:46
 * @Description:
 */
public interface IHashMap {
    public void put(String key,Object object);
    public Object get(String key);
}

Entry代碼

package Test.testtest;

/**
 * @Auther: 李景然
 * @Date: 2018/5/24 22:46
 * @Description:
 */
public class Entry {

    public Entry(Object key, Object value) {
        super();
        this.key = key;
        this.value = value;
    }
    public Object key;
    public Object value;
    @Override
    public String toString() {
        return "[key=" + key + ", value=" + value + "]";
    }

}

MyHashMap代碼

package Test.testtest;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * @Auther: 李景然
 * @Date: 2018/5/24 22:47
 * @Description:
 */
public class MyHashMap implements IHashMap {
    private Object[] objects=new Object[2000];

    public MyHashMap(){
    }

    @Override
    public void put(String key, Object object) {
        int hashCode=HashCode.hashcode(key);
        if(objects[hashCode]==null){
            //key值相同的時候,把value存放到鏈表中
            LinkedList<Entry> list=new LinkedList<>();
            list.add(new Entry(key,object));
            objects[hashCode]=list;
        }else{
            LinkedList<Entry> list=(LinkedList<Entry>)objects[hashCode];
            list.add(new Entry(key,object));
            objects[hashCode]=list;
        }
    }

    @Override
    public Object get(String key) {
        int hashCode=HashCode.hashcode(key);
        Object object=objects[hashCode];
        if(object!=null){
            LinkedList<Entry> list=(LinkedList<Entry>)object;
            for (Entry e:list){
                if(e.key.equals(key)){
                    return e.value;
                }
            }
        }
        return null;
    }
}

內容查找性能比較

重復前面的 練習-查找內容性能比較 ,不過不使用HashMap,而是使用上個練習中自定義的MyHashMap.

準備一個ArrayList其中存放100000(十萬個)Hero對象,其名稱是隨機的,格式是hero-[4位隨機數]
hero-3229
hero-6232
hero-9365
...

因為總數很大,所以幾乎每種都有重復,把名字叫做 hero-5555的所有對象找出來
要求使用兩種辦法來尋找
1. 不使用MyHashMap,直接使用for循環找出來,並統計花費的時間
2. 借助MyHashMap,找出結果,並統計花費的時間

MyHashMapTest代碼

package Test.testtest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Auther: 李景然
 * @Date: 2018/5/24 23:04
 * @Description:
 */
public class MyHashMapTest {
    public static void main(String[] args) {
        //初始化100000個英雄對象
        ArrayList<Hero> arrayList=new ArrayList<>();
        for (int i=0;i<100000;i++){
            String str="hero-"+String.valueOf(Math.random()*9000+1000).substring(0,4);
            arrayList.add(new Hero(str));
        }

        //初始化HashMap
        HashMap<String,ArrayList<Hero>> hashMap=new HashMap<>();
        for (Hero h:arrayList){
            ArrayList<Hero> list=hashMap.get(h.getName());
            if(list==null){
                list=new ArrayList<>();
                hashMap.put(h.getName(),list);
            }
            list.add(h);
        }

        //初始化MyHashMap
        MyHashMap myHashMap=new MyHashMap();
        for (Hero h:arrayList){
            ArrayList<Hero> list=(ArrayList<Hero>)(myHashMap.get(h.getName()));
            if(list==null){
                list=new ArrayList<>();
                myHashMap.put(h.getName(),list);
            }
            list.add(h);
        }

        searchOfHashMapTest(hashMap,"hero-5555");
        searchOfMyHashMapTest(myHashMap,"hero-5555");

    }

    //使用hashMap循環來找hero-5555
    private static void searchOfHashMapTest(HashMap<String,ArrayList<Hero>> hashMap,String key){
        double startTime=System.currentTimeMillis();
        ArrayList<Hero> list=hashMap.get(key);
        double endTime=System.currentTimeMillis();
        if(list!=null){
            System.out.println("使用hashMap循環來找hero-5555:找到"+list.size()+"個;用時:"+(endTime-startTime)+"毫秒");
        }else{
            System.out.println("使用hashMap循環來找hero-5555:找到"+0+"個;用時:"+(endTime-startTime)+"毫秒");
        }

    }

    //使用myHashMap循環來找hero-5555
    private static void searchOfMyHashMapTest(MyHashMap myHashMap,String key){
        double startTime=System.currentTimeMillis();
        int count=0;
        ArrayList<Hero> list=(ArrayList<Hero>) myHashMap.get(key);
        double endTime=System.currentTimeMillis();
        if(list!=null){
            System.out.println("使用myHashMap循環來找hero-5555:找到"+list.size()+"個;用時:"+(endTime-startTime)+"毫秒");
        }else{
            System.out.println("使用myHashMap循環來找hero-5555:找到"+0+"個;用時:"+(endTime-startTime)+"毫秒");
        }

    }

運行結果:

技術分享圖片

雖然用HashMap或者MyHashMap查詢速度快,幾乎不花費時間。但是要用HashMap或者MyHashMap,我們首先要把ArrayList中的數據轉移到HashMap或者MyHashMap中,這個也需要消耗時間,降低性能(從這個中可以看出 練習-查找內容性能比較)

java集合框架之HashCode