如何在不克隆的情况下获取子数组

Posted

技术标签:

【中文标题】如何在不克隆的情况下获取子数组【英文标题】:How to get sub array without cloning 【发布时间】:2020-08-08 20:36:40 【问题描述】:

我知道在 C# 中,我们总是可以使用Array.Copy() 方法获取给定数组的子数组。但是,这将消耗更多的内存和处理时间,这在只读情况下是不必要的。例如,我正在编写一个负载很重的网络程序,它会非常频繁地与集群中的其他节点交换消息。每条消息的前 20 个字节是消息头,其余字节组成消息体。因此,我将接收到的原始消息分为头字节数组和正文字节数组,以便分别处理。但是,这显然会消耗双倍的内存和额外的时间。在 C 中,我们可以轻松地使用指针并为其分配偏移量来访问数组的不同部分。 例如,在 C 语言中,如果我们有一个char a[] = "ABCDEFGHIJKLMN",我们可以声明一个char* ptr = a + 3 来表示数组DEFGHIJKLMN

有没有办法在 C# 中实现这一点?

【问题讨论】:

可能对unsafe 感兴趣。得到一个可枚举的投影很容易,但不是另一个“没有副本”的数组。 “但是,这显然会消耗双倍内存” 我不明白为什么,除非你在内存中保留两个副本。将其拆分后,将原始的设置为 null 或让它超出范围。 查看 LINQ 的 Skip() 和 Take() 扩展方法。不用担心数组本身,学习使用 IEnumerable。 您可以定义自己的小类型来包装目标数组+偏移量,并通过添加偏移量的数组访问。 unsafe 或创建自己的数组,不复制实例只是引用它们。 【参考方案1】:

您可能对ArraySegmentsunsafe 感兴趣。


ArraySegments 分隔一维数组的一部分。

Check ArraySegments in action

ArraySegments 使用示例:

 int[] array =  10, 20, 30 ;

 ArraySegment<int> segment = new ArraySegment<int>(array, 1, 2);
 // The segment contains offset = 1, count = 2 and range =  20, 30 

Unsafe 定义一个可以使用指针的不安全上下文。

不安全的使用示例:

    int[] a =  4, 5, 6, 7, 8 ;

    unsafe
    
        fixed (int* c = a)
        
            // use the pointer
        
    

【讨论】:

请注意,在 .NET 4.5 (Visual Studio 2012) 中进行了改进,请参阅 what is the use of ArraySegment&lt;T&gt; class?(但它不是 class,而是现在实现接口的 struct)。跨度> 【参考方案2】:

首先,您必须将其视为过早的优化。

但是如果你确定你真的需要它,你可以使用几种方法来减少内存消耗:

1) 您可以使用享元模式https://en.wikipedia.org/wiki/Flyweight_pattern 来池化重复资源。

2) 您可以尝试使用 unsafe 指令和手动指针管理。

3) 您可以切换到 C 来实现此功能,然后从您的 C# 程序中调用本机代码。

根据我的经验,短期对象的内存消耗不是一个大问题,之后我会使用享元模式和配置文件应用程序编写代码。

【讨论】:

“从你的 C# 程序调用本机代码”不应该是最好的建议。大多数情况下,外部本机调用 (DllImport) 会导致一些问题,您无法在运行时调试它... 我明白这一点,我的第一个建议是“根本不要考虑这种内存消耗”。此外,我更喜欢使用日志记录来代替调试器,因此查看正在发生的事情并将这种方法扩展到您的应用程序的全功能日志记录系统并不是什么大问题。【参考方案3】:

假设您在 C# 中有一个消息包装类?为什么不在它上面添加一个名为 header 的属性,它返回前 20 个字节。

如果您将整个初始数组放在内存数组中,您可以使用上面 Jonathon Reinhart 建议的 skip and take 轻松完成此操作,但听起来您可能将它放在网络流中,这意味着该属性可能有点通过从流中读取最初的 20 个字节来参与更多。

类似的东西:

class Message

    private readonly Stream _stream;
    private byte[] _inMemoryBytes;

    public Message(Stream stream)
    
        _stream = stream;
    

    public IEnumerable<byte> Header
    
        get
        
            if (_inMemoryBytes.Length >= 20)
                return _inMemoryBytes.Take(20);

            _stream.Read(_inMemoryBytes, 0, 20);
            return _inMemoryBytes.Take(20);
        
    

    public IEnumerable<byte> FullMessage
    
        get
        
            // Read and return the whole message. You might want amend to data already read.
        
    

【讨论】:

以上是关于如何在不克隆的情况下获取子数组的主要内容,如果未能解决你的问题,请参考以下文章

node.js如何删除数组子文档的元素?

如何在不渲染子组件的情况下根据子组件大小更改 React 父组件大小?

如何在不使用服务的情况下将子组件之间的反应式表单数据传递给父组件

如何在不从当前活动选项卡中获取焦点的情况下将子窗口添加到 QMdiArea(设置为 TAB 模式)?

在不打开子shell的情况下获取当前时间(和日期)[重复]

如何在不进行硬编码的情况下在 C++ 中获取类数组的长度?