假设您确定您的 OutOfMemoryException 可以避免,并且您将其全部存储在内存中的方法不是实际问题(如果内存可用,GC 非常擅长阻止这种情况发生)......
- 这是你的第一个问题。我不确定 CLR 是否支持任何大于 2 GB 的单个对象。
- 其次,您在谈论“有些被保留,有些被GC'd”。即,一旦完成“子数组”,您希望能够重新分配数组的元素。
- 第三,我假设
PointF[] giganticList = new PointF[100];
您的问题中的PointF[] giganticList = new PointF[1000000];
?
还可以考虑使用MemoryFailPoint
,因为这允许您“要求”内存并检查异常,而不是因 OutOfMemoryException 而崩溃。
编辑也许最重要的是,您现在正在进入权衡取舍。如果你这样做,你可能会开始失去像抖动优化for
循环这样的优势,方法是在循环开始时进行数组绑定检查(for (int i= 0; i < myArray.Length; i++)
得到优化,int length = 5; for (int i= 0; i < length; i++)
没有)。如果您有大量计算资源代码,那么这可能会伤害您。您还必须更加努力地并行处理不同的子数组。创建子数组的副本,或者它们的部分,甚至它们中的项目,仍然会分配更多的内存,这些内存将被 GC 处理。
这可以通过包装数组并跟踪哪些部分用于哪些子数组来实现。您本质上是在谈论分配一大块内存,然后重用其中的一部分而不让 GC 承担责任。您可以利用ArraySegment<T>
,但这会带来其自身的潜在问题,例如将原始数组暴露给所有调用者。
这不会是简单的,但它是可能的。可能不是每次删除子数组时,您都希望通过移动其他子数组来缩小主数组的碎片以缩小间隙(或者在连续段用完时执行此操作)。
一个简单的例子看起来像下面的伪代码(未经测试,如果你的电脑离开家并且你的猫爆炸了,不要怪我)。还有另外两种方法,我在最后提到。
public class ArrayCollection {
List<int> startIndexes = new List<int>();
List<int> lengths = new List<int>();
const int 1beeellion = 100;
PointF[] giganticList = new PointF[1beeellion];
public ArraySegment<PointF> this[int childIndex] {
get {
// Care with this method, ArraySegment exposes the original array, which callers could then
// do bad things to
return new ArraySegment<String>(giganticList, startIndexes[childIndex], length[childIndex]);
}}
// returns the index of the child array
public int AddChild(int length) {
// TODO: needs to take account of lists with no entries yet
int startIndex = startIndexes.Last() + lengths.Last();
// TODO: check that startIndex + length is not more than giganticIndex
// If it is then
// find the smallest unused block which is larger than the length requested
// or defrag our unused array sections
// otherwise throw out of memory
startIndexes.Add(startIndex); // will need inserts for defrag operations
lengths.Add(length); // will need inserts for defrag operations
return startIndexes.Count - 1; // inserts will need to return inserted index
}
public ArraySegment<PointF> GetChildAsSegment(int childIndex) {
// Care with this method, ArraySegment exposes the original array, which callers could then
// do bad things to
return new ArraySegment<String>(giganticList, startIndexes[childIndex], length[childIndex]);
}
public void SetChildValue(int childIndex, int elementIndex, PointF value) {
// TODO: needs to take account of lists with no entries yet, or invalid childIndex
// TODO: check and PREVENT buffer overflow (see warning) here and in other methods
// e.g.
if (elementIndex >= lengths[childIndex]) throw new YouAreAnEvilCallerException();
int falseZeroIndex = startIndexes[childIndex];
giganticList[falseZeroIndex + elementIndex];
}
public PointF GetChildValue(int childIndex, int elementIndex) {
// TODO: needs to take account of lists with no entries yet, bad child index, element index
int falseZeroIndex = startIndexes[childIndex];
return giganticList[falseZeroIndex + elementIndex];
}
public void RemoveChildArray(int childIndex) {
startIndexes.RemoveAt(childIndex);
lengths.RemoveAt(childIndex);
// TODO: possibly record the unused segment in another pair of start, length lists
// to allow for defraging in AddChildArray
}
}
警告 上面的代码有效地引入了childIndex
缓冲区溢出漏洞,例如,如果您没有length
在SetChildValue
. 在尝试在生产中执行此操作之前,您必须了解并防止它unsafe
发生,尤其是在将这些方法与使用.
现在,这可以扩展为返回子数组的伪索引public PointF this[int index]
方法、子数组的枚举器等,但正如我所说,这变得越来越复杂,您需要确定它是否真的能解决您的问题。您的大部分时间将花在重用(第一)碎片整理(第二)扩展(第三)throw OutOfMemory
(最后)逻辑上。
如果我对 2GB 对象限制的评论是正确的,这种方法还有一个优点,即您可以分配许多 2GB 子数组并将它们用作单个数组。
这假设您不想沿着unsafe
路线走并使用指针,但效果是一样的,您只需创建一个包装类来管理固定内存块中的子数组。
另一种方法是使用哈希集/字典方法。分配整个(2GB 大数组)并将其分成块(比如 100 个数组元素)。然后,子数组将分配多个块,并在其最终块中浪费一些空间。这将产生一些整体空间浪费的影响(取决于您的平均“子长度与块长度”预测),但是您可以增加和减少子数组的大小以及删除和插入影响较小的子数组的优势关于你的碎片化。
值得注意的参考资料:
将数组作为不同类型的数组或结构访问的其他示例。这些的实现可能会帮助您开发自己的解决方案
数组优化
并行数组和使用unsafe