如何直接获取List<byte>的底层byte[]数组以避免拷贝?
解决方案
1. 利用.NET内置API直接获取List的底层数组(.NET 5+)
在.NET 5及以上版本,可通过System.Runtime.InteropServices.CollectionsMarshal的GetValueList方法,直接拿到List
- 返回的数组长度是List的容量(Capacity),而非实际元素数(Count)。如果你的业务允许返回的数组长度大于等于有效元素数,仅使用前
Count个元素,那这种方式完全避免拷贝。 - 拿到底层数组后,绝对不能再操作原List对象,否则会引发不可预测的错误。
示例代码:
using System.Runtime.InteropServices; public byte[] Serialize() { write_buffer = new List<byte>(65536); item.Serialize(this); // 获取底层数组 byte[] underlyingArray = CollectionsMarshal.GetValueList(write_buffer); // 清空原List引用,防止后续误操作 write_buffer = null; return underlyingArray; }
如果业务必须返回长度恰好等于Count的数组,这种方法无法避免拷贝——因为需要从底层数组中截取有效元素,这一步必然要复制数据。
2. 自行实现可控的字节缓冲区
原生List
示例实现:
public class ByteBuffer { private byte[] _buffer; private int _count; private const int DefaultCapacity = 65536; public ByteBuffer(int initialCapacity = DefaultCapacity) { _buffer = new byte[initialCapacity]; _count = 0; } public void Add(byte value) { EnsureCapacity(_count + 1); _buffer[_count++] = value; } public void AddRange(ReadOnlySpan<byte> values) { EnsureCapacity(_count + values.Length); values.CopyTo(_buffer.AsSpan(_count)); _count += values.Length; } private void EnsureCapacity(int requiredCapacity) { if (_buffer.Length >= requiredCapacity) return; // 可自定义扩容策略,比如按1.5倍或2倍扩容 int newCapacity = Math.Max(requiredCapacity, _buffer.Length * 2); byte[] newBuffer = new byte[newCapacity]; Array.Copy(_buffer, newBuffer, _count); _buffer = newBuffer; } // 转移内部数组所有权,调用后缓冲区不可再使用 public byte[] TakeBuffer() { byte[] result = _buffer; // 清空内部引用,避免后续操作影响返回的数组 _buffer = null; _count = 0; return result; } public int Count => _count; }
修改后的Serialize方法:
ByteBuffer write_buffer; T item; public byte[] Serialize() { write_buffer = new ByteBuffer(65536); item.Serialize(this); // 需要调整Serialize方法,使其操作ByteBuffer而非List<byte> return write_buffer.TakeBuffer(); }
这种方式完全由你控制数组所有权转移,无额外拷贝,且不需要unsafe代码。
3. 关于非unsafe的类似realloc的方法
.NET中没有和C语言realloc完全等价的方法,因为.NET数组是固定长度的,无法直接调整现有数组大小。不过可以通过以下方式模拟内存复用效果:
- 使用
ArrayPool<byte>租赁数组:扩容时租赁更大的数组,拷贝旧数组内容后归还旧数组,能减少GC压力,比直接创建新数组更高效。 - 自行实现扩容逻辑:如同上述ByteBuffer类,按需创建新数组并拷贝旧数据,本质和List
扩容逻辑一致,但你可以自定义策略,且能直接返回内部数组。
需要注意的是,若要将数组长度精确调整为实际元素数,无论哪种方式都无法避免拷贝——除非你能保证扩容后的数组长度恰好等于最终元素数。
内容的提问来源于stack exchange,提问作者Tenobaal
相关产品推荐
相关产品推荐

