it编程 > 数据库 > Redis

redis存储空间复杂度和时间复杂度的平衡

27人参与 2026-02-28 Redis

下面是一个案例:根据奖品概率计算奖品存储空间以及时间复杂度的权衡.

1. 内存占用的计算

1.1 不同精度下的内存占用

// 精度范围(raterange)决定了数组大小
raterange = 10000      // 万分位 (0.0001)
raterange = 100000     // 十万分位 (0.00001)
raterange = 1000000    // 百万分位 (0.000001)

1.2 具体计算

精度raterange数组长度int类型占用总内存
万分位10,00010,0004 bytes40 kb
十万分位100,000100,0004 bytes400 kb
百万分位1,000,0001,000,0004 bytes4 mb
千万分位10,000,00010,000,0004 bytes40 mb
亿分位100,000,000100,000,0004 bytes400 mb

1.3 实际项目中的影响

场景1:100个奖品,万分位精度

raterange = 10000
awardcount = 100
slotsperaward = 100  // 每个奖品100个槽位

内存占用 = 10000 × 4 bytes = 40 kb
✅ 可接受

场景2:100个奖品,十万分位精度

raterange = 100000
awardcount = 100
slotsperaward = 1000  // 每个奖品1000个槽位

内存占用 = 100000 × 4 bytes = 400 kb
✅ 可接受,但增长明显

场景3:100个奖品,百万分位精度

raterange = 1000000
awardcount = 100
slotsperaward = 10000  // 每个奖品10000个槽位

内存占用 = 1000000 × 4 bytes = 4 mb
⚠️ 开始需要注意

场景4:1000个奖品,十万分位精度

raterange = 100000
awardcount = 1000
slotsperaward = 100  // 每个奖品100个槽位

内存占用 = 100000 × 4 bytes = 400 kb
✅ 可接受

场景5:1000个奖品,百万分位精度

raterange = 1000000
awardcount = 1000
slotsperaward = 1000  // 每个奖品1000个槽位

内存占用 = 1000000 × 4 bytes = 4 mb
⚠️ 内存占用较大

2. o(1) vs o(logn) 内存对比

2.1 o(1) 数组算法内存占用

// 存储随机数到奖品的映射
int[] awardmappingarray = new int[raterange];  // 固定大小数组

// 例如:raterange = 1000000
// 内存 = 1000000 × 4 bytes = 4 mb

特点

2.2 o(logn) 前缀和算法内存占用

// 存储奖品信息和前缀和
list<strategyawardentity> awards = new arraylist<>();  // 奖品列表
double[] prefixsums = new double[awardcount];          // 前缀和数组

// 例如:awardcount = 1000
// 内存 = 1000 × (award对象大小 + 8 bytes) ≈ 100 kb

特点

2.3 内存对比表

对比项o(1) 数组算法o(logn) 前缀和算法
内存占用o(raterange)o(awardcount)
万分位(10k奖品)40 kb~1 mb(奖品对象)
十万位(1k奖品)400 kb~100 kb
百万位(100奖品)4 mb~10 kb
关系与精度成正比与奖品数量成正比

2.4 关键发现

重要结论

raterange >> awardcount 时,o(logn) 更节省内存
raterange ≈ awardcount 时,两者内存相近

典型场景

万分位 + 100奖品: raterange(10000) > awardcount(100)  → o(1)更省内存
万分位 + 1万奖品: raterange(10000) ≈ awardcount(10000) → 差不多
万分位 + 10万奖品: raterange(10000) < awardcount(100000) → o(logn)更省内存

3. 时间和空间的权衡

3.1 trade-off 示意图

内存占用
  ↑
  │                    o(1)数组算法
  │                  /
  │                /
  │              /
  │            /
  │          /
  │        /
  │      /
  │    /
  │  /
  │/
  └─────────────────────────────────→ 精度(raterange)
     低    中    高    非常高

o(logn)算法内存几乎不变

3.2 时间复杂度对比

操作o(1) 数组算法o(logn) 前缀和算法
预热o(raterange)o(awardcount × log awardcount)
单次抽奖o(1)o(log awardcount)
空间o(raterange)o(awardcount)

3.3 决策矩阵

场景raterangeawardcount推荐算法原因
110,000100o(1)内存小(40kb),查询快
2100,000100o(1)内存可接受(400kb),查询快
31,000,000100o(1)内存较大(4mb),但奖品少
410,00010,000两者皆可内存相近,看查询频率
510,000100,000o(logn)o(1)内存太大(40mb)
6100,0001,000o(logn)o(1)内存太大(400kb)
71,000,0001,000o(logn)o(1)内存太大(4mb)

4. 实际代码中的体现

4.1 o(1) 算法实现(固定数组)

/**
 * o(1)抽奖算法 - 预热时生成固定大小数组
 * 优点:查询时间o(1)
 * 缺点:内存占用与raterange成正比
 */
public integer rafflestrategyo1(long strategyid, 
        list<strategyawardentity> strategyawardentities) {
    
    // 1. 获取精度范围
    bigdecimal minawardrate = minawardrate(strategyawardentities);
    int raterange = convert(minawardrate);  // 例如:10000, 100000, 1000000
    
    // 2. 分配数组(内存占用 = raterange × 4 bytes)
    int[] strategyawardraterandom = new int[raterange];
    
    // 3. 填充数组
    int currentindex = 0;
    for (strategyawardentity award : strategyawardentities) {
        // 计算该奖品应该占用的槽位数
        int awardslots = (int) (award.getawardrate() * raterange);
        
        // 填充槽位
        for (int i = 0; i < awardslots; i++) {
            if (currentindex >= raterange) break;
            strategyawardawardraterandom[currentindex++] = award.getawardid();
        }
    }
    
    // 4. 随机打乱(消除初始顺序偏差)
    shuffle(strategyawardawardraterandom);
    
    // 5. 抽奖(o(1)时间复杂度)
    int randomindex = threadlocalrandom.current().nextint(raterange);
    return strategyawardawardraterandom[randomindex];
}

4.2 o(logn) 算法实现(前缀和)

/**
 * o(logn)抽奖算法 - 实时计算前缀和
 * 优点:内存占用与awardcount成正比,与raterange无关
 * 缺点:查询时间o(logn),需要每次计算
 */
public integer rafflestrategylogn(long strategyid, 
        list<strategyawardentity> strategyawardentities) {
    
    // 1. 计算最小精度(用于确定随机数范围)
    bigdecimal minawardrate = minawardrate(strategyawardentities);
    int raterange = convert(minawardrate);  // 例如:100000, 1000000
    
    // 2. 构建前缀和数组(内存占用 = 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. 生成随机数
    int randomvalue = threadlocalrandom.current().nextint(raterange);
    double randomrate = (double) randomvalue / raterange;
    
    // 4. 二分查找(o(logn)时间复杂度)
    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 算法选择(综合考虑)

/**
 * 综合考虑槽位数和内存占用的算法选择
 */
public integer rafflestrategy(long strategyid, 
        list<strategyawardentity> strategyawardentities) {
    
    // 1. 计算精度范围
    bigdecimal minawardrate = minawardrate(strategyawardentities);
    int raterange = convert(minawardrate);  // 10000, 100000, 1000000...
    
    int awardcount = strategyawardentities.size();
    int slotsperaward = raterange / awardcount;
    
    // 2. 计算内存占用
    long o1memorybytes = (long) raterange * 4;  // o(1)数组内存
    long lognmemorybytes = (long) awardcount * 40;  // o(logn)奖品对象内存估算
    
    // 3. 算法选择条件
    // 条件1:槽位数 >= 10(保证公平性)
    // 条件2:内存占用可接受(o(1)算法不超过10mb)
    if (slotsperaward >= 10 && o1memorybytes <= 10 * 1024 * 1024) {
        // 使用o(1)算法
        return rafflestrategyo1(strategyid, strategyawardentities);
    } else {
        // 使用o(logn)算法
        return rafflestrategylogn(strategyid, strategyawardentities);
    }
}

5. 内存优化方案

5.1 压缩存储

/**
 * 内存优化:如果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压缩存储
 * 例如:[a,a,a,b,b,c,c,c,c] → [(a,3), (b,2), (c,4)]
 */
class compressedarray {
    int[] awardids;     // 奖品id数组(去重后的)
    int[] runlengths;   // 每个奖品连续出现的次数
    
    // 随机抽奖
    public int raffle() {
        // 1. 计算总长度
        int totallength = arrays.stream(runlengths).sum();
        
        // 2. 生成随机位置
        int randomposition = threadlocalrandom.current().nextint(totallength);
        
        // 3. 查找对应的奖品
        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 分级缓存

/**
 * 分级缓存策略:根据访问频率选择不同的存储方式
 */
public class tieredstrategycache {
    
    // 热数据:高频访问的奖品,使用o(1)数组
    private int[] hotawardsarray;
    
    // 冷数据:低频访问的奖品,使用o(logn)列表
    private list<strategyawardentity> coldawardslist;
    
    // 访问统计
    private map<long, atomicinteger> accesscountmap = new concurrenthashmap<>();
    
    // 定期调整热冷数据
    public void rebalance() {
        // 统计访问频率
        map<long, integer> sortedawards = accesscountmap.entryset().stream()
            .sorted(map.entry.comparingbyvalue())
            .limit(100)  // 取访问最多的100个奖品
            .collect(collectors.tomap(
                map.entry::getkey,
                map.entry::getvalue,
                (e1, e2) -> e1,
                linkedhashmap::new
            ));
        
        // 将高频奖品放入热数据
        rebuildhotawardsarray(sortedawards.keyset());
    }
}

5.3 懒加载

/**
 * 懒加载策略:只有首次访问时才加载数据
 */
public class lazystrategycache {
    
    private volatile int[] cachedarray = null;
    private volatile list<strategyawardentity> cachedawards = null;
    
    // 懒加载o(1)数组
    public int[] geto1array(list<strategyawardentity> awards) {
        if (cachedarray == null) {
            synchronized (this) {
                if (cachedarray == null) {
                    // 只在首次访问时计算
                    cachedarray = buildstrategyarray(awards);
                }
            }
        }
        return cachedarray;
    }
    
    // 懒加载o(logn)列表
    public list<strategyawardentity> getawardslist() {
        if (cachedawards == null) {
            synchronized (this) {
                if (cachedawards == null) {
                    cachedawards = loadawardsfromdb();
                }
            }
        }
        return cachedawards;
    }
}

6. 总结

您提出的问题非常关键,总结几点:

6.1 内存占用的核心问题

o(1)算法内存 = raterange × 4 bytes
raterange越大,内存占用越大

o(logn)算法内存 = awardcount × 奖品对象大小
与raterange无关,只与奖品数量有关

6.2 算法选择的影响因素

因素o(1)算法o(logn)算法
内存与精度成正比与奖品数量成正比
时间o(1)快o(logn)稍慢
公平性依赖槽位数实时计算,更公平
复杂度实现简单需要前缀和+二分查找

6.3 最佳实践

  1. 小精度(万分位):优先使用o(1),内存小(40kb),查询快

  2. 大精度(十万分位+)

    • 奖品数量少(100以内) → o(1)可接受
    • 奖品数量多(1000以上) → o(logn)更省内存
  3. 超高精度(百万分位+):建议使用o(logn),内存更可控

6.4 内存优化建议

  1. 压缩存储:使用rle等压缩算法
  2. 分级缓存:热数据用o(1),冷数据用o(logn)
  3. 懒加载:首次访问时再加载
  4. 动态调整:根据实际运行情况选择算法

这就是为什么算法选择需要综合考虑时间复杂度、空间复杂度、公平性等多个因素的原因。

到此这篇关于redis存储空间复杂度和时间复杂度的平衡的文章就介绍到这了,更多相关redis存储空间复杂度和时间复杂度内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

您想发表意见!!点此发布评论

推荐阅读

Redis Key过期删除策略使用小结

02-28

Redis 热 Key 问题的解决

02-28

Redis之BigKey与HotKey问题详解

02-28

基于setnx,lua脚本和Redisson详解Redis分布式锁实现的三种方式

03-02

Redis中Cluster的容错性的实现

02-12

Redisson延迟队列实现订单关闭的操作方法

03-05

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论