斐波那契雜湊演演算法和hashMap實踐

2022-11-27 18:06:34

斐波那契雜湊和hashMap實踐

適合的場景:抽獎(遊戲、輪盤、活動促銷等等)

如果有不對的地方,歡迎指正!

HashMap實現資料雜湊:

設定專案,引入pom.xml:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.58</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.5</version>
</dependency>

前置條件:

  1. 存放陣列:128位元
  2. 100個資料進行雜湊
  3. 若有hash衝突,使用拉鍊法

首先,初始化100個亂數,這裡採用雪花演演算法snowFlake,採用靈活註解除參照,宣告為Component,

簡單瞭解下SnowFlake工具類實現方式:

import com.example.containstest.containsTestDemo.mapper.FileNameAndType;
import com.example.containstest.containsTestDemo.mapper.FileNameInsertMapper;
import com.example.containstest.containsTestDemo.pojo.DatagenertionDao;
import com.example.containstest.containsTestDemo.pojo.FileNameType;
import com.example.containstest.containsTestDemo.utils.SnowFlake;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class SnowFlake implements IIdGenerator {

    private Snowflake snowflake;

    @PostConstruct
    public void init(){
        // 0 ~ 31 位,可以採用設定的方式使用
        long workerId;
        try {
            workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
        }catch (Exception e){
            workerId = NetUtil.getLocalhostStr().hashCode();
        }
        workerId = workerId >> 16 & 31;
        long dataCenterId = 1L;
        snowflake = IdUtil.createSnowflake(workerId,dataCenterId);
    }


    @Override
    public long nextId() {
        return snowflake.nextId();
    }
}

迴圈100,取其亂數儲存列表中:

List<String> list = new ArrayList<>();
//儲存idx和重複的值
Map<Integer, String> map = new HashMap<>();
for(int i = 0; i < 101; i++){
    list.add(String.valueOf(snowFlake.nextId()));
}

建立資料雜湊到的陣列大小,這裡取128

//定義要存放的陣列 模擬初始化為128
String[] res = new String[128];

遍歷儲存的陣列,計算出當前數值的hash值,然後到陣列對應的下標處對應;

  1. 為空。當前key賦值到該陣列下標值
  2. 不為空,表示hash衝突,這裡採用字串拼接模擬碰撞後使用的拉鍊法
  3. map儲存對應idxkey
  4. 對重複的雜湊的值進行排序輸出
for(String key : list){
    //計算hash值,未使用擾動函數
    int idx = key.hashCode() & (res.length - 1);
    log.info("key的值{},idx的值{}",key,idx);
    if(null == res[idx]){
        res[idx] = key;
        continue;
    }
    res[idx] = res[idx] +"->" + key;
    map.put(idx,res[idx]);
}
//排序
mapSort(map);

map排序:

private void mapSort(Map<Integer, String> map) {
    // 按照Map的鍵進行排序
    Map<Integer, String> sortedMap = map.entrySet().stream()
            .sorted(Map.Entry.comparingByKey())
            .collect(
                    Collectors.toMap(
                            Map.Entry::getKey,
                            Map.Entry::getValue,
                            (oldVal, newVal) -> oldVal,
                            LinkedHashMap::new
                    )
            );
    log.info("====>HashMap雜湊演演算法碰撞資料:{}",JSON.toJSONString(sortedMap));
}

未使用擾動函數HashMap雜湊輸出結果展示:

{
    28: "1596415617815183397->1596415617815183430",
    29: "1596415617815183398->1596415617815183431",
    30: "1596415617815183399->1596415617815183432",
    59: "1596415617815183363->1596415617815183440",
    60: "1596415617815183364->1596415617815183441",
    61: "1596415617815183365->1596415617815183442",
    62: "1596415617815183366->1596415617815183443",
    63: "1596415617815183367->1596415617815183400->1596415617815183444",
    64: "1596415617815183368->1596415617815183401->1596415617815183445",
    65: "1596415617815183369->1596415617815183402->1596415617815183446",
    66: "1596415617815183403->1596415617815183447",
    67: "1596415617815183404->1596415617815183448",
    68: "1596415617815183405->1596415617815183449",
    90: "1596415617815183373->1596415617815183450",
    91: "1596415617815183374->1596415617815183451",
    92: "1596415617815183375->1596415617815183452",
    93: "1596415617815183376->1596415617815183453",
    94: "1596415617815183377->1596415617815183410->1596415617815183454",
    95: "1596415617815183378->1596415617815183411->1596415617815183455",
    96: "1596415617815183379->1596415617815183412->1596415617815183456",
    97: "1596415617815183413->1596415617815183457",
    98: "1596415617815183414->1596415617815183458",
    99: "1596415617815183415->1596415617815183459",
    121: "1596415617815183383->1596415617815183460",
    125: "1596415617815183387->1596415617815183420",
    126: "1596415617815183388->1596415617815183421",
    127: "1596415617815183389->1596415617815183422"
}

針對上述程式碼,修改int idx = key.hashCode() & (res.length - 1);為下面:

int idx =  (res.length - 1) & (key.hashCode() ^ (key.hashCode() >>> 16));

使用擾動函數HashMap雜湊輸出結果展示:

{
    44: "1596518378456121344->1596518378456121388",
    67: "1596518378460315650->1596518378460315694",
    72: "1596518378456121351->1596518378456121395",
    73: "1596518378456121350->1596518378456121394",
    83: "1596518378456121345->1596518378456121389",
    92: "1596518378460315651->1596518378460315695",
    93: "1596518378460315652->1596518378460315696"
}

從對比結果可以看到,加入擾動函數後hash碰撞減少了很多。

斐波那契雜湊演演算法

前置條件:

  1. 生成模擬資料:隨機且不重複的100個數
  2. 宣告雜湊陣列:大小128
  3. 若有hash衝突,儲存map,方便資料檢視

靜態變數宣告:

//黃金分割點
private static final int HASH_INCREMENT = 0x61c88647;
private static int range = 100;

按照慣例,初始化陣列,模擬資料;

List<Integer> listThreadLocal = new ArrayList<>();

Map<Integer, String> map = new HashMap<>();
//定義要存放的陣列 模擬初始化為128
Integer[] result = new Integer[128];
result = getNumber(range);
//......

//-----方法
/**
 * 隨機生成100以內不重複的數
 * @param total
 * @return
 */
public static Integer[] getNumber(int total){
    Integer[] NumberBox = new Integer[total];			//容器A
    Integer[] rtnNumber = new Integer[total];			//容器B

    for (int i = 0; i < total; i++){
        NumberBox[i] = i;		//先把N個數放入容器A中
    }
    Integer end = total - 1;

    for (int j = 0; j < total; j++){
        int num = new Random().nextInt(end + 1);	//取亂數
        rtnNumber[j] = NumberBox[num];			//把亂數放入容器B
        NumberBox[num] = NumberBox[end];			//把容器A中最後一個數覆蓋所取的亂數
        end--;									//縮小亂數所取範圍
    }
    return rtnNumber;							//返回int型陣列
}

遍歷模擬的資料,通過原始碼閱讀,可以找到new ThreadLocal<String>().set("xbhog");

注意點,threadLocal實現主要是在ThreadLoacalMap中

//2
private final int threadLocalHashCode = nextHashCode();
//4  預設值0
private static AtomicInteger nextHashCode = new AtomicInteger();
//3步驟使用
private static final int HASH_INCREMENT = 0x61c88647;

//3
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

//key和len是外部傳入  1
int i = key.threadLocalHashCode & (len-1);

可以看到每次計算雜湊值的時候,都會加一次HASH_INCREMENT黃金分割點,來更好的雜湊資料,然後模擬該操作:程式碼如下

for(int i = 0; i < listThreadLocal.size(); i++){
    hashCode = listThreadLocal.get(i) * HASH_INCREMENT + HASH_INCREMENT;
    Integer idx = (hashCode & 127);
    log.info("key的值{},idx的值{}",listThreadLocal.get(i),idx);
    if(null == result[idx]){
        result[idx] = listThreadLocal.get(i);
        continue;
    }
    String idxInRes = map.get(idx);
    String idxInRess = idxInRes + "->" + listThreadLocal.get(i);
    map.put(idx,idxInRess);
}

進行衝突後重復值排序

//map排序
if(CollectionUtil.isEmpty(map)){
    log.info("斐波那契額雜湊資料集:{}",JSON.toJSONString(result));
    System.out.println("===》無重複資料,不需要排序");
    return;
}
mapSort(map);

使用斐波那契雜湊演演算法輸出結果展示:

斐波那契額雜湊資料集:38,15,29,22,55,86,70,64,47,32,67,7,60,85,97,95,58,46,14,83,12,72,18,96,36,20,76,59,6,33,50,30,23,42,81,31,66,71,82,61,53,84,41,45,74,63,89,77,90,16,8,37,1,62,65,99,51,78,91,39,5,57,27,56,44,13,92,25,0,24,80,3,94,26,40,34,73,35,88,2,87,11,93,54,69,68,10,17,43,48,19,9,79,21,98,52,4,28,75,49]
===》無重複資料,不需要排序

由上我們可以看到,沒有重複的資料,全部比較完美的雜湊到不同的地方。

參考文章:

https://blog.csdn.net/qq_26327971/article/details/104757316

https://juejin.cn/post/6844903985808146439

https://juejin.cn/user/3913917126415166