斐波那契雜湊和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>
前置條件:
- 存放陣列:128位
- 100個資料進行雜湊
- 若有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
值,然後到陣列對應的下標處對應;
- 為空。當前
key
賦值到該陣列下標值 - 不為空,表示
hash
衝突,這裡採用字串拼接模擬碰撞後使用的拉鍊法 map
儲存對應idx
和key
值- 對重複的雜湊的值進行排序輸出
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
碰撞減少了很多。
斐波那契雜湊演算法
前置條件:
- 生成模擬資料:隨機且不重複的100個數
- 宣告雜湊陣列:大小128
- 若有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