You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何直接获取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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 14:48:11