如何实现可自动整理为范围的内存高效型自定义Int+Range列表
这思路太赞了!对于连续数字占比高的场景,用范围(Range)替代单个数字存储,确实能把内存占用压到极低。我来一步步带你实现这个自定义集合,核心就是自动合并相邻/重叠范围的逻辑,还会包含常用的操作方法:
1. 先定义轻量的Range基础结构
我们用值类型struct来实现Range,它内存开销极小(仅两个int的大小),还能直接栈分配。同时给它加些实用方法,方便后续合并和判断:
public struct Range { public int Start { get; } public int End { get; } // 单个数字直接转成Range(起始=结束) public Range(int singleValue) : this(singleValue, singleValue) { } // 构造连续范围的Range public Range(int start, int end) { if (start > end) throw new ArgumentOutOfRangeException(nameof(start), "起始值不能大于结束值"); Start = start; End = end; } // 判断当前范围是否包含某个数字 public bool Contains(int value) => value >= Start && value <= End; // 判断是否能和另一个Range合并(相邻或重叠都算) public bool CanMergeWith(Range other) { // 比如1-5和6-10(相邻)、1-5和3-7(重叠)都可以合并 return other.Start <= End + 1 && other.End >= Start - 1; } // 合并两个Range成一个新范围 public Range MergeWith(Range other) { if (!CanMergeWith(other)) throw new InvalidOperationException("无法合并不相邻/不重叠的Range"); return new Range(Math.Min(Start, other.Start), Math.Max(End, other.End)); } // 重写ToString,方便调试查看 public override string ToString() => Start == End ? $"{Start}" : $"{Start}-{End}"; }
2. 实现核心的紧凑数字集合类
这个类的关键是添加元素时自动合并范围,同时保持内部Range列表的有序性,方便后续合并和查询:
public class CompactNumberCollection { // 内部用List存Range,因为List本身的内存效率就很高 private readonly List<Range> _ranges = new List<Range>(); // 添加单个数字的快捷方法 public void Add(int value) => Add(new Range(value)); // 添加一个Range的核心方法 public void Add(Range range) { if (_ranges.Count == 0) { _ranges.Add(range); return; } // 先找出所有能和当前Range合并的现有范围 var mergeTargets = new List<Range>(); foreach (var existing in _ranges) { if (existing.CanMergeWith(range)) mergeTargets.Add(existing); } // 如果有可合并的范围,先移除它们,再和当前范围合并成新的大Range if (mergeTargets.Count > 0) { foreach (var target in mergeTargets) { _ranges.Remove(target); range = range.MergeWith(target); } } // 把合并后的Range插入到有序位置,保证列表始终按Start升序排列 InsertSorted(range); } // 按Start升序插入,方便后续快速查找和合并 private void InsertSorted(Range range) { int insertIndex = _ranges.FindIndex(r => r.Start > range.Start); if (insertIndex == -1) _ranges.Add(range); else _ranges.Insert(insertIndex, range); } // 检查某个数字是否存在(用二分查找优化性能,因为列表是有序的) public bool Contains(int value) { int left = 0; int right = _ranges.Count - 1; while (left <= right) { int mid = (left + right) / 2; var currentRange = _ranges[mid]; if (currentRange.Contains(value)) return true; if (value < currentRange.Start) right = mid - 1; else left = mid + 1; } return false; } // 枚举所有数字(用yield返回,不会生成完整数组,内存友好) public IEnumerable<int> GetAllNumbers() { foreach (var range in _ranges) { for (int i = range.Start; i <= range.End; i++) { yield return i; } } } // 对外暴露只读的Range列表,防止外部修改内部结构 public IReadOnlyList<Range> Ranges => _ranges.AsReadOnly(); // 重写ToString,方便调试时查看集合内容 public override string ToString() => string.Join(", ", _ranges.Select(r => r.ToString())); }
3. 实际使用示例
来看看这个集合的自动合并效果:
var compactCollection = new CompactNumberCollection(); // 随便加一些数字和范围 compactCollection.Add(1); compactCollection.Add(2); compactCollection.Add(3); compactCollection.Add(5); compactCollection.Add(new Range(7, 10)); compactCollection.Add(6); // 会自动和5、7-10合并成5-10 compactCollection.Add(new Range(12, 15)); compactCollection.Add(11); // 自动合并成11-15 // 输出集合内容:1-3, 5-10, 11-15 Console.WriteLine(compactCollection); // 检查数字是否存在 Console.WriteLine(compactCollection.Contains(8)); // 输出True Console.WriteLine(compactCollection.Contains(4)); // 输出False // 遍历所有数字(不会生成完整数组,内存友好) foreach (var num in compactCollection.GetAllNumbers()) { Console.Write($"{num} "); } // 输出:1 2 3 5 6 7 8 9 10 11 12 13 14 15
4. 内存效率对比(直观感受)
比如存储1-3000、3002、4000-5000:
- 用
List<int>:总共是3000+1+1001=4002个int,每个int占4字节,总内存16008字节(约15.6KB) - 用
CompactNumberCollection:只存3个Range,每个Range占8字节,总内存24字节,差距一目了然
5. 可选优化方向
如果需要更灵活的功能,可以考虑这些扩展:
- 支持移除操作:删除数字或范围时,需要拆分现有Range,逻辑稍复杂但可行
- 泛型适配:把int换成long、short等数值类型,适配更大范围的数字
- 线程安全:多线程场景下,添加锁或改用线程安全的内部集合
内容的提问来源于stack exchange,提问作者windowsgm
相关产品推荐
相关产品推荐

