redis存儲(chǔ)空間復(fù)雜度和時(shí)間復(fù)雜度的平衡
下面是一個(gè)案例:根據(jù)獎(jiǎng)品概率計(jì)算獎(jiǎng)品存儲(chǔ)空間以及時(shí)間復(fù)雜度的權(quán)衡.
1. 內(nèi)存占用的計(jì)算
1.1 不同精度下的內(nèi)存占用
// 精度范圍(rateRange)決定了數(shù)組大小 rateRange = 10000 // 萬(wàn)分位 (0.0001) rateRange = 100000 // 十萬(wàn)分位 (0.00001) rateRange = 1000000 // 百萬(wàn)分位 (0.000001)
1.2 具體計(jì)算
| 精度 | rateRange | 數(shù)組長(zhǎng)度 | int類型占用 | 總內(nèi)存 |
|---|---|---|---|---|
| 萬(wàn)分位 | 10,000 | 10,000 | 4 bytes | 40 KB |
| 十萬(wàn)分位 | 100,000 | 100,000 | 4 bytes | 400 KB |
| 百萬(wàn)分位 | 1,000,000 | 1,000,000 | 4 bytes | 4 MB |
| 千萬(wàn)分位 | 10,000,000 | 10,000,000 | 4 bytes | 40 MB |
| 億分位 | 100,000,000 | 100,000,000 | 4 bytes | 400 MB |
1.3 實(shí)際項(xiàng)目中的影響
場(chǎng)景1:100個(gè)獎(jiǎng)品,萬(wàn)分位精度
rateRange = 10000 awardCount = 100 slotsPerAward = 100 // 每個(gè)獎(jiǎng)品100個(gè)槽位 內(nèi)存占用 = 10000 × 4 bytes = 40 KB ? 可接受
場(chǎng)景2:100個(gè)獎(jiǎng)品,十萬(wàn)分位精度
rateRange = 100000 awardCount = 100 slotsPerAward = 1000 // 每個(gè)獎(jiǎng)品1000個(gè)槽位 內(nèi)存占用 = 100000 × 4 bytes = 400 KB ? 可接受,但增長(zhǎng)明顯
場(chǎng)景3:100個(gè)獎(jiǎng)品,百萬(wàn)分位精度
rateRange = 1000000 awardCount = 100 slotsPerAward = 10000 // 每個(gè)獎(jiǎng)品10000個(gè)槽位 內(nèi)存占用 = 1000000 × 4 bytes = 4 MB ?? 開始需要注意
場(chǎng)景4:1000個(gè)獎(jiǎng)品,十萬(wàn)分位精度
rateRange = 100000 awardCount = 1000 slotsPerAward = 100 // 每個(gè)獎(jiǎng)品100個(gè)槽位 內(nèi)存占用 = 100000 × 4 bytes = 400 KB ? 可接受
場(chǎng)景5:1000個(gè)獎(jiǎng)品,百萬(wàn)分位精度
rateRange = 1000000 awardCount = 1000 slotsPerAward = 1000 // 每個(gè)獎(jiǎng)品1000個(gè)槽位 內(nèi)存占用 = 1000000 × 4 bytes = 4 MB ?? 內(nèi)存占用較大
2. O(1) vs O(logn) 內(nèi)存對(duì)比
2.1 O(1) 數(shù)組算法內(nèi)存占用
// 存儲(chǔ)隨機(jī)數(shù)到獎(jiǎng)品的映射 int[] awardMappingArray = new int[rateRange]; // 固定大小數(shù)組 // 例如:rateRange = 1000000 // 內(nèi)存 = 1000000 × 4 bytes = 4 MB
特點(diǎn):
- 連續(xù)內(nèi)存:數(shù)組是連續(xù)內(nèi)存分配
- 固定大小:一旦分配,大小固定
- 快速訪問(wèn):直接通過(guò)索引訪問(wèn),O(1)時(shí)間復(fù)雜度
2.2 O(logn) 前綴和算法內(nèi)存占用
// 存儲(chǔ)獎(jiǎng)品信息和前綴和 List<StrategyAwardEntity> awards = new ArrayList<>(); // 獎(jiǎng)品列表 double[] prefixSums = new double[awardCount]; // 前綴和數(shù)組 // 例如:awardCount = 1000 // 內(nèi)存 = 1000 × (award對(duì)象大小 + 8 bytes) ≈ 100 KB
特點(diǎn):
- 動(dòng)態(tài)大小:根據(jù)獎(jiǎng)品數(shù)量分配
- 只存獎(jiǎng)品:不存儲(chǔ)隨機(jī)數(shù)映射,只存獎(jiǎng)品本身
- 計(jì)算查詢:每次抽獎(jiǎng)需要計(jì)算前綴和并二分查找
2.3 內(nèi)存對(duì)比表
| 對(duì)比項(xiàng) | O(1) 數(shù)組算法 | O(logn) 前綴和算法 |
|---|---|---|
| 內(nèi)存占用 | O(rateRange) | O(awardCount) |
| 萬(wàn)分位(10K獎(jiǎng)品) | 40 KB | ~1 MB(獎(jiǎng)品對(duì)象) |
| 十萬(wàn)位(1K獎(jiǎng)品) | 400 KB | ~100 KB |
| 百萬(wàn)位(100獎(jiǎng)品) | 4 MB | ~10 KB |
| 關(guān)系 | 與精度成正比 | 與獎(jiǎng)品數(shù)量成正比 |
2.4 關(guān)鍵發(fā)現(xiàn)
重要結(jié)論:
rateRange >> awardCount 時(shí),O(logn) 更節(jié)省內(nèi)存 rateRange ≈ awardCount 時(shí),兩者內(nèi)存相近
典型場(chǎng)景:
萬(wàn)分位 + 100獎(jiǎng)品: rateRange(10000) > awardCount(100) → O(1)更省內(nèi)存
萬(wàn)分位 + 1萬(wàn)獎(jiǎng)品: rateRange(10000) ≈ awardCount(10000) → 差不多
萬(wàn)分位 + 10萬(wàn)獎(jiǎng)品: rateRange(10000) < awardCount(100000) → O(logn)更省內(nèi)存
3. 時(shí)間和空間的權(quán)衡
3.1 Trade-off 示意圖
內(nèi)存占用
↑
│ O(1)數(shù)組算法
│ /
│ /
│ /
│ /
│ /
│ /
│ /
│ /
│ /
│/
└─────────────────────────────────→ 精度(rateRange)
低 中 高 非常高O(logn)算法內(nèi)存幾乎不變
3.2 時(shí)間復(fù)雜度對(duì)比
| 操作 | O(1) 數(shù)組算法 | O(logn) 前綴和算法 |
|---|---|---|
| 預(yù)熱 | O(rateRange) | O(awardCount × log awardCount) |
| 單次抽獎(jiǎng) | O(1) | O(log awardCount) |
| 空間 | O(rateRange) | O(awardCount) |
3.3 決策矩陣
| 場(chǎng)景 | rateRange | awardCount | 推薦算法 | 原因 |
|---|---|---|---|---|
| 1 | 10,000 | 100 | O(1) | 內(nèi)存小(40KB),查詢快 |
| 2 | 100,000 | 100 | O(1) | 內(nèi)存可接受(400KB),查詢快 |
| 3 | 1,000,000 | 100 | O(1) | 內(nèi)存較大(4MB),但獎(jiǎng)品少 |
| 4 | 10,000 | 10,000 | 兩者皆可 | 內(nèi)存相近,看查詢頻率 |
| 5 | 10,000 | 100,000 | O(logn) | O(1)內(nèi)存太大(40MB) |
| 6 | 100,000 | 1,000 | O(logn) | O(1)內(nèi)存太大(400KB) |
| 7 | 1,000,000 | 1,000 | O(logn) | O(1)內(nèi)存太大(4MB) |
4. 實(shí)際代碼中的體現(xiàn)
4.1 O(1) 算法實(shí)現(xiàn)(固定數(shù)組)
/**
* O(1)抽獎(jiǎng)算法 - 預(yù)熱時(shí)生成固定大小數(shù)組
* 優(yōu)點(diǎn):查詢時(shí)間O(1)
* 缺點(diǎn):內(nèi)存占用與rateRange成正比
*/
public Integer raffleStrategyO1(Long strategyId,
List<StrategyAwardEntity> strategyAwardEntities) {
// 1. 獲取精度范圍
BigDecimal minAwardRate = minAwardRate(strategyAwardEntities);
int rateRange = convert(minAwardRate); // 例如:10000, 100000, 1000000
// 2. 分配數(shù)組(內(nèi)存占用 = rateRange × 4 bytes)
int[] strategyAwardRateRandom = new int[rateRange];
// 3. 填充數(shù)組
int currentIndex = 0;
for (StrategyAwardEntity award : strategyAwardEntities) {
// 計(jì)算該獎(jiǎng)品應(yīng)該占用的槽位數(shù)
int awardSlots = (int) (award.getAwardRate() * rateRange);
// 填充槽位
for (int i = 0; i < awardSlots; i++) {
if (currentIndex >= rateRange) break;
strategyAwardAwardRateRandom[currentIndex++] = award.getAwardId();
}
}
// 4. 隨機(jī)打亂(消除初始順序偏差)
shuffle(strategyAwardAwardRateRandom);
// 5. 抽獎(jiǎng)(O(1)時(shí)間復(fù)雜度)
int randomIndex = ThreadLocalRandom.current().nextInt(rateRange);
return strategyAwardAwardRateRandom[randomIndex];
}
4.2 O(logn) 算法實(shí)現(xiàn)(前綴和)
/**
* O(logn)抽獎(jiǎng)算法 - 實(shí)時(shí)計(jì)算前綴和
* 優(yōu)點(diǎn):內(nèi)存占用與awardCount成正比,與rateRange無(wú)關(guān)
* 缺點(diǎn):查詢時(shí)間O(logn),需要每次計(jì)算
*/
public Integer raffleStrategyLogn(Long strategyId,
List<StrategyAwardEntity> strategyAwardEntities) {
// 1. 計(jì)算最小精度(用于確定隨機(jī)數(shù)范圍)
BigDecimal minAwardRate = minAwardRate(strategyAwardEntities);
int rateRange = convert(minAwardRate); // 例如:100000, 1000000
// 2. 構(gòu)建前綴和數(shù)組(內(nèi)存占用 = awardCount × 8 bytes)
double[] awardRates = new double[strategyAwardEntities.size()];
double[] prefixSums = new double[strategyAwardEntities.size()];
for (int i = 0; i < strategyAwardEntities.size(); i++) {
StrategyAwardEntity award = strategyAwardEntities.get(i);
awardRates[i] = award.getAwardRate();
if (i == 0) {
prefixSums[i] = awardRates[i];
} else {
prefixSums[i] = prefixSums[i - 1] + awardRates[i];
}
}
// 3. 生成隨機(jī)數(shù)
int randomValue = ThreadLocalRandom.current().nextInt(rateRange);
double randomRate = (double) randomValue / rateRange;
// 4. 二分查找(O(logn)時(shí)間復(fù)雜度)
int left = 0;
int right = prefixSums.length - 1;
while (left < right) {
int mid = (left + right) / 2;
if (prefixSums[mid] < randomRate) {
left = mid + 1;
} else {
right = mid;
}
}
return strategyAwardEntities.get(left).getAwardId();
}
4.3 算法選擇(綜合考慮)
/**
* 綜合考慮槽位數(shù)和內(nèi)存占用的算法選擇
*/
public Integer raffleStrategy(Long strategyId,
List<StrategyAwardEntity> strategyAwardEntities) {
// 1. 計(jì)算精度范圍
BigDecimal minAwardRate = minAwardRate(strategyAwardEntities);
int rateRange = convert(minAwardRate); // 10000, 100000, 1000000...
int awardCount = strategyAwardEntities.size();
int slotsPerAward = rateRange / awardCount;
// 2. 計(jì)算內(nèi)存占用
long o1MemoryBytes = (long) rateRange * 4; // O(1)數(shù)組內(nèi)存
long lognMemoryBytes = (long) awardCount * 40; // O(logn)獎(jiǎng)品對(duì)象內(nèi)存估算
// 3. 算法選擇條件
// 條件1:槽位數(shù) >= 10(保證公平性)
// 條件2:內(nèi)存占用可接受(O(1)算法不超過(guò)10MB)
if (slotsPerAward >= 10 && o1MemoryBytes <= 10 * 1024 * 1024) {
// 使用O(1)算法
return raffleStrategyO1(strategyId, strategyAwardEntities);
} else {
// 使用O(logn)算法
return raffleStrategyLogn(strategyId, strategyAwardEntities);
}
}
5. 內(nèi)存優(yōu)化方案
5.1 壓縮存儲(chǔ)
/**
* 內(nèi)存優(yōu)化:如果rateRange非常大,使用壓縮算法
*/
public Integer raffleStrategyCompressed(Long strategyId,
List<StrategyAwardEntity> strategyAwardEntities) {
BigDecimal minAwardRate = minAwardRate(strategyAwardEntities);
int rateRange = convert(minAwardRate);
// 如果rateRange > 1000000,使用Run-Length Encoding壓縮
if (rateRange > 1000000) {
return raffleStrategyCompressed(strategyId, strategyAwardEntities);
}
// 正常O(1)算法
return raffleStrategyO1(strategyId, strategyAwardEntities);
}
/**
* Run-Length Encoding壓縮存儲(chǔ)
* 例如:[A,A,A,B,B,C,C,C,C] → [(A,3), (B,2), (C,4)]
*/
class CompressedArray {
int[] awardIds; // 獎(jiǎng)品ID數(shù)組(去重后的)
int[] runLengths; // 每個(gè)獎(jiǎng)品連續(xù)出現(xiàn)的次數(shù)
// 隨機(jī)抽獎(jiǎng)
public int raffle() {
// 1. 計(jì)算總長(zhǎng)度
int totalLength = Arrays.stream(runLengths).sum();
// 2. 生成隨機(jī)位置
int randomPosition = ThreadLocalRandom.current().nextInt(totalLength);
// 3. 查找對(duì)應(yīng)的獎(jiǎng)品
int currentPosition = 0;
for (int i = 0; i < runLengths.length; i++) {
currentPosition += runLengths[i];
if (randomPosition < currentPosition) {
return awardIds[i];
}
}
return awardIds[awardIds.length - 1];
}
}
5.2 分級(jí)緩存
/**
* 分級(jí)緩存策略:根據(jù)訪問(wèn)頻率選擇不同的存儲(chǔ)方式
*/
public class TieredStrategyCache {
// 熱數(shù)據(jù):高頻訪問(wèn)的獎(jiǎng)品,使用O(1)數(shù)組
private int[] hotAwardsArray;
// 冷數(shù)據(jù):低頻訪問(wèn)的獎(jiǎng)品,使用O(logn)列表
private List<StrategyAwardEntity> coldAwardsList;
// 訪問(wèn)統(tǒng)計(jì)
private Map<Long, AtomicInteger> accessCountMap = new ConcurrentHashMap<>();
// 定期調(diào)整熱冷數(shù)據(jù)
public void rebalance() {
// 統(tǒng)計(jì)訪問(wèn)頻率
Map<Long, Integer> sortedAwards = accessCountMap.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.limit(100) // 取訪問(wèn)最多的100個(gè)獎(jiǎng)品
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));
// 將高頻獎(jiǎng)品放入熱數(shù)據(jù)
rebuildHotAwardsArray(sortedAwards.keySet());
}
}
5.3 懶加載
/**
* 懶加載策略:只有首次訪問(wèn)時(shí)才加載數(shù)據(jù)
*/
public class LazyStrategyCache {
private volatile int[] cachedArray = null;
private volatile List<StrategyAwardEntity> cachedAwards = null;
// 懶加載O(1)數(shù)組
public int[] getO1Array(List<StrategyAwardEntity> awards) {
if (cachedArray == null) {
synchronized (this) {
if (cachedArray == null) {
// 只在首次訪問(wèn)時(shí)計(jì)算
cachedArray = buildStrategyArray(awards);
}
}
}
return cachedArray;
}
// 懶加載O(logn)列表
public List<StrategyAwardEntity> getAwardsList() {
if (cachedAwards == null) {
synchronized (this) {
if (cachedAwards == null) {
cachedAwards = loadAwardsFromDB();
}
}
}
return cachedAwards;
}
}
6. 總結(jié)
您提出的問(wèn)題非常關(guān)鍵,總結(jié)幾點(diǎn):
6.1 內(nèi)存占用的核心問(wèn)題
O(1)算法內(nèi)存 = rateRange × 4 bytes
rateRange越大,內(nèi)存占用越大O(logn)算法內(nèi)存 = awardCount × 獎(jiǎng)品對(duì)象大小
與rateRange無(wú)關(guān),只與獎(jiǎng)品數(shù)量有關(guān)
6.2 算法選擇的影響因素
| 因素 | O(1)算法 | O(logn)算法 |
|---|---|---|
| 內(nèi)存 | 與精度成正比 | 與獎(jiǎng)品數(shù)量成正比 |
| 時(shí)間 | O(1)快 | O(logn)稍慢 |
| 公平性 | 依賴槽位數(shù) | 實(shí)時(shí)計(jì)算,更公平 |
| 復(fù)雜度 | 實(shí)現(xiàn)簡(jiǎn)單 | 需要前綴和+二分查找 |
6.3 最佳實(shí)踐
小精度(萬(wàn)分位):優(yōu)先使用O(1),內(nèi)存小(40KB),查詢快
大精度(十萬(wàn)分位+):
- 獎(jiǎng)品數(shù)量少(100以內(nèi)) → O(1)可接受
- 獎(jiǎng)品數(shù)量多(1000以上) → O(logn)更省內(nèi)存
超高精度(百萬(wàn)分位+):建議使用O(logn),內(nèi)存更可控
6.4 內(nèi)存優(yōu)化建議
- 壓縮存儲(chǔ):使用RLE等壓縮算法
- 分級(jí)緩存:熱數(shù)據(jù)用O(1),冷數(shù)據(jù)用O(logn)
- 懶加載:首次訪問(wèn)時(shí)再加載
- 動(dòng)態(tài)調(diào)整:根據(jù)實(shí)際運(yùn)行情況選擇算法
這就是為什么算法選擇需要綜合考慮時(shí)間復(fù)雜度、空間復(fù)雜度、公平性等多個(gè)因素的原因。
到此這篇關(guān)于redis存儲(chǔ)空間復(fù)雜度和時(shí)間復(fù)雜度的平衡的文章就介紹到這了,更多相關(guān)redis存儲(chǔ)空間復(fù)雜度和時(shí)間復(fù)雜度內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis?HyperLogLog數(shù)據(jù)統(tǒng)計(jì)輕量級(jí)解決方案詳解
這篇文章主要為大家介紹了Redis?HyperLogLog數(shù)據(jù)統(tǒng)計(jì)輕量級(jí)解決方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
Redis 緩存實(shí)現(xiàn)存儲(chǔ)和讀取歷史搜索關(guān)鍵字的操作方法
這篇文章主要介紹了Redis 緩存實(shí)現(xiàn)存儲(chǔ)和讀取歷史搜索關(guān)鍵字,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
基于redis 7.2.3的makefile源碼解讀學(xué)習(xí)
這篇文章主要為大家介紹了基于redis 7.2.3的makefile源碼解讀學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
淺談Redis位圖(Bitmap)及Redis二進(jìn)制中的問(wèn)題
這篇文章主要介紹了Redis位圖(Bitmap)及Redis二進(jìn)制中的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Redis瞬時(shí)高并發(fā)秒殺方案總結(jié)
本文講述了Redis瞬時(shí)高并發(fā)秒殺方案總結(jié),具有很好的參考價(jià)值,感興趣的小伙伴們可以參考一下,具體如下:2018-05-05

