Arduino伺服电机SD卡离线动作库:从数据持久化到多轴协同控制
2026/6/1 15:51:59
下面是一个案例:根据奖品概率计算奖品存储空间以及时间复杂度的权衡.
// 精度范围(rateRange)决定了数组大小rateRange=10000// 万分位 (0.0001)rateRange=100000// 十万分位 (0.00001)rateRange=1000000// 百万分位 (0.000001)| 精度 | rateRange | 数组长度 | int类型占用 | 总内存 |
|---|---|---|---|---|
| 万分位 | 10,000 | 10,000 | 4 bytes | 40 KB |
| 十万分位 | 100,000 | 100,000 | 4 bytes | 400 KB |
| 百万分位 | 1,000,000 | 1,000,000 | 4 bytes | 4 MB |
| 千万分位 | 10,000,000 | 10,000,000 | 4 bytes | 40 MB |
| 亿分位 | 100,000,000 | 100,000,000 | 4 bytes | 400 MB |
场景1:100个奖品,万分位精度
rateRange=10000awardCount=100slotsPerAward=100// 每个奖品100个槽位内存占用=10000×4bytes=40KB ✅ 可接受场景2:100个奖品,十万分位精度
rateRange=100000awardCount=100slotsPerAward=1000// 每个奖品1000个槽位内存占用=100000×4bytes=400KB ✅ 可接受,但增长明显场景3:100个奖品,百万分位精度
rateRange=1000000awardCount=100slotsPerAward=10000// 每个奖品10000个槽位内存占用=1000000×4bytes=4MB ⚠️ 开始需要注意场景4:1000个奖品,十万分位精度
rateRange=100000awardCount=1000slotsPerAward=100// 每个奖品100个槽位内存占用=100000×4bytes=400KB ✅ 可接受场景5:1000个奖品,百万分位精度
rateRange=1000000awardCount=1000slotsPerAward=1000// 每个奖品1000个槽位内存占用=1000000×4bytes=4MB ⚠️ 内存占用较大// 存储随机数到奖品的映射int[]awardMappingArray=newint[rateRange];// 固定大小数组// 例如:rateRange = 1000000// 内存 = 1000000 × 4 bytes = 4 MB特点:
// 存储奖品信息和前缀和List<StrategyAwardEntity>awards=newArrayList<>();// 奖品列表double[]prefixSums=newdouble[awardCount];// 前缀和数组// 例如:awardCount = 1000// 内存 = 1000 × (award对象大小 + 8 bytes) ≈ 100 KB特点:
| 对比项 | O(1) 数组算法 | O(logn) 前缀和算法 |
|---|---|---|
| 内存占用 | O(rateRange) | O(awardCount) |
| 万分位(10K奖品) | 40 KB | ~1 MB(奖品对象) |
| 十万位(1K奖品) | 400 KB | ~100 KB |
| 百万位(100奖品) | 4 MB | ~10 KB |
| 关系 | 与精度成正比 | 与奖品数量成正比 |
重要结论:
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)更省内存内存占用 ↑ │ O(1)数组算法 │ / │ / │ / │ / │ / │ / │ / │ / │ / │/ └─────────────────────────────────→ 精度(rateRange) 低 中 高 非常高 O(logn)算法内存几乎不变| 操作 | O(1) 数组算法 | O(logn) 前缀和算法 |
|---|---|---|
| 预热 | O(rateRange) | O(awardCount × log awardCount) |
| 单次抽奖 | O(1) | O(log awardCount) |
| 空间 | O(rateRange) | O(awardCount) |
| 场景 | rateRange | awardCount | 推荐算法 | 原因 |
|---|---|---|---|---|
| 1 | 10,000 | 100 | O(1) | 内存小(40KB),查询快 |
| 2 | 100,000 | 100 | O(1) | 内存可接受(400KB),查询快 |
| 3 | 1,000,000 | 100 | O(1) | 内存较大(4MB),但奖品少 |
| 4 | 10,000 | 10,000 | 两者皆可 | 内存相近,看查询频率 |
| 5 | 10,000 | 100,000 | O(logn) | O(1)内存太大(40MB) |
| 6 | 100,000 | 1,000 | O(logn) | O(1)内存太大(400KB) |
| 7 | 1,000,000 | 1,000 | O(logn) | O(1)内存太大(4MB) |
/** * O(1)抽奖算法 - 预热时生成固定大小数组 * 优点:查询时间O(1) * 缺点:内存占用与rateRange成正比 */publicIntegerraffleStrategyO1(LongstrategyId,List<StrategyAwardEntity>strategyAwardEntities){// 1. 获取精度范围BigDecimalminAwardRate=minAwardRate(strategyAwardEntities);intrateRange=convert(minAwardRate);// 例如:10000, 100000, 1000000// 2. 分配数组(内存占用 = rateRange × 4 bytes)int[]strategyAwardRateRandom=newint[rateRange];// 3. 填充数组intcurrentIndex=0;for(StrategyAwardEntityaward:strategyAwardEntities){// 计算该奖品应该占用的槽位数intawardSlots=(int)(award.getAwardRate()*rateRange);// 填充槽位for(inti=0;i<awardSlots;i++){if(currentIndex>=rateRange)break;strategyAwardAwardRateRandom[currentIndex++]=award.getAwardId();}}// 4. 随机打乱(消除初始顺序偏差)shuffle(strategyAwardAwardRateRandom);// 5. 抽奖(O(1)时间复杂度)intrandomIndex=ThreadLocalRandom.current().nextInt(rateRange);returnstrategyAwardAwardRateRandom[randomIndex];}/** * O(logn)抽奖算法 - 实时计算前缀和 * 优点:内存占用与awardCount成正比,与rateRange无关 * 缺点:查询时间O(logn),需要每次计算 */publicIntegerraffleStrategyLogn(LongstrategyId,List<StrategyAwardEntity>strategyAwardEntities){// 1. 计算最小精度(用于确定随机数范围)BigDecimalminAwardRate=minAwardRate(strategyAwardEntities);intrateRange=convert(minAwardRate);// 例如:100000, 1000000// 2. 构建前缀和数组(内存占用 = awardCount × 8 bytes)double[]awardRates=newdouble[strategyAwardEntities.size()];double[]prefixSums=newdouble[strategyAwardEntities.size()];for(inti=0;i<strategyAwardEntities.size();i++){StrategyAwardEntityaward=strategyAwardEntities.get(i);awardRates[i]=award.getAwardRate();if(i==0){prefixSums[i]=awardRates[i];}else{prefixSums[i]=prefixSums[i-1]+awardRates[i];}}// 3. 生成随机数intrandomValue=ThreadLocalRandom.current().nextInt(rateRange);doublerandomRate=(double)randomValue/rateRange;// 4. 二分查找(O(logn)时间复杂度)intleft=0;intright=prefixSums.length-1;while(left<right){intmid=(left+right)/2;if(prefixSums[mid]<randomRate){left=mid+1;}else{right=mid;}}returnstrategyAwardEntities.get(left).getAwardId();}/** * 综合考虑槽位数和内存占用的算法选择 */publicIntegerraffleStrategy(LongstrategyId,List<StrategyAwardEntity>strategyAwardEntities){// 1. 计算精度范围BigDecimalminAwardRate=minAwardRate(strategyAwardEntities);intrateRange=convert(minAwardRate);// 10000, 100000, 1000000...intawardCount=strategyAwardEntities.size();intslotsPerAward=rateRange/awardCount;// 2. 计算内存占用longo1MemoryBytes=(long)rateRange*4;// O(1)数组内存longlognMemoryBytes=(long)awardCount*40;// O(logn)奖品对象内存估算// 3. 算法选择条件// 条件1:槽位数 >= 10(保证公平性)// 条件2:内存占用可接受(O(1)算法不超过10MB)if(slotsPerAward>=10&&o1MemoryBytes<=10*1024*1024){// 使用O(1)算法returnraffleStrategyO1(strategyId,strategyAwardEntities);}else{// 使用O(logn)算法returnraffleStrategyLogn(strategyId,strategyAwardEntities);}}/** * 内存优化:如果rateRange非常大,使用压缩算法 */publicIntegerraffleStrategyCompressed(LongstrategyId,List<StrategyAwardEntity>strategyAwardEntities){BigDecimalminAwardRate=minAwardRate(strategyAwardEntities);intrateRange=convert(minAwardRate);// 如果rateRange > 1000000,使用Run-Length Encoding压缩if(rateRange>1000000){returnraffleStrategyCompressed(strategyId,strategyAwardEntities);}// 正常O(1)算法returnraffleStrategyO1(strategyId,strategyAwardEntities);}/** * Run-Length Encoding压缩存储 * 例如:[A,A,A,B,B,C,C,C,C] → [(A,3), (B,2), (C,4)] */classCompressedArray{int[]awardIds;// 奖品ID数组(去重后的)int[]runLengths;// 每个奖品连续出现的次数// 随机抽奖publicintraffle(){// 1. 计算总长度inttotalLength=Arrays.stream(runLengths).sum();// 2. 生成随机位置intrandomPosition=ThreadLocalRandom.current().nextInt(totalLength);// 3. 查找对应的奖品intcurrentPosition=0;for(inti=0;i<runLengths.length;i++){currentPosition+=runLengths[i];if(randomPosition<currentPosition){returnawardIds[i];}}returnawardIds[awardIds.length-1];}}/** * 分级缓存策略:根据访问频率选择不同的存储方式 */publicclassTieredStrategyCache{// 热数据:高频访问的奖品,使用O(1)数组privateint[]hotAwardsArray;// 冷数据:低频访问的奖品,使用O(logn)列表privateList<StrategyAwardEntity>coldAwardsList;// 访问统计privateMap<Long,AtomicInteger>accessCountMap=newConcurrentHashMap<>();// 定期调整热冷数据publicvoidrebalance(){// 统计访问频率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());}}/** * 懒加载策略:只有首次访问时才加载数据 */publicclassLazyStrategyCache{privatevolatileint[]cachedArray=null;privatevolatileList<StrategyAwardEntity>cachedAwards=null;// 懒加载O(1)数组publicint[]getO1Array(List<StrategyAwardEntity>awards){if(cachedArray==null){synchronized(this){if(cachedArray==null){// 只在首次访问时计算cachedArray=buildStrategyArray(awards);}}}returncachedArray;}// 懒加载O(logn)列表publicList<StrategyAwardEntity>getAwardsList(){if(cachedAwards==null){synchronized(this){if(cachedAwards==null){cachedAwards=loadAwardsFromDB();}}}returncachedAwards;}}您提出的问题非常关键,总结几点:
O(1)算法内存 = rateRange × 4 bytes rateRange越大,内存占用越大 O(logn)算法内存 = awardCount × 奖品对象大小 与rateRange无关,只与奖品数量有关| 因素 | O(1)算法 | O(logn)算法 |
|---|---|---|
| 内存 | 与精度成正比 | 与奖品数量成正比 |
| 时间 | O(1)快 | O(logn)稍慢 |
| 公平性 | 依赖槽位数 | 实时计算,更公平 |
| 复杂度 | 实现简单 | 需要前缀和+二分查找 |
小精度(万分位):优先使用O(1),内存小(40KB),查询快
大精度(十万分位+):
超高精度(百万分位+):建议使用O(logn),内存更可控
这就是为什么算法选择需要综合考虑时间复杂度、空间复杂度、公平性等多个因素的原因。