连接字节 [] 的 C# 列表

Posted

技术标签:

【中文标题】连接字节 [] 的 C# 列表【英文标题】:Concatenating a C# List of byte[] 【发布时间】:2011-06-20 01:04:05 【问题描述】:

我正在创建几个字节数组,需要将它们连接在一起以创建一个大字节数组 - 我根本不想使用 byte[],但这里别无选择...

我在创建它们时将每个都添加到列表中,因此我只需在拥有所有字节 [] 后进行连接,但我的问题是,实际执行此操作的最佳方法是什么?

当我有一个包含未知数量字节 [] 的列表并且我想将它们全部连接在一起时。

谢谢。

【问题讨论】:

【参考方案1】:
listOfByteArrs.SelectMany(byteArr=>byteArr).ToArray()

上面的代码会将一系列字节序列连接成一个序列 - 并将结果存储在一个数组中。

虽然可读,但这并不是最有效的 - 它没有利用您已经知道结果字节数组的长度这一事实,因此可以避免动态扩展的 .ToArray() 实现必然涉及多个分配和数组副本。此外,SelectMany 是根据迭代器实现的;这意味着很多+很多接口调用非常慢。但是,对于较小的数据集大小,这不太重要。

如果您需要更快的实现,您可以执行以下操作:

var output = new byte[listOfByteArrs.Sum(arr=>arr.Length)];
int writeIdx=0;
foreach(var byteArr in listOfByteArrs) 
    byteArr.CopyTo(output, writeIdx);
    writeIdx += byteArr.Length;

或者正如马蒂尼奥建议的那样:

var output = new byte[listOfByteArrs.Sum(arr => arr.Length)];
using(var stream = new MemoryStream(output))
    foreach (var bytes in listOfByteArrs)
        stream.Write(bytes, 0, bytes.Length);

一些时间安排:

var listOfByteArrs = Enumerable.Range(1,1000)
    .Select(i=>Enumerable.Range(0,i).Select(x=>(byte)x).ToArray()).ToList();

在我的机器上使用 short 方法连接这 500500 个字节需要 15 毫秒,使用快速方法需要 0.5 毫秒 - YMMV,请注意,对于许多应用程序来说,两者都足够快 ;-)。

最后,您可以将 Array.CopyTo 替换为 static Array.Copy、低级 Buffer.BlockCopy 或带有预分配后台缓冲区的 MemoryStream - 这些在我的测试(x64 .NET 4.0)。

【讨论】:

虽然简短明了,但请注意,与传统解决方案相比,此代码非常慢。如果它足够快,那很好,但它可能不够快。 哪个是“传统解决方案”? “传统”解决方案可能是手动的,嵌套的 for 循环。这比基于块复制的解决方案慢了大约三倍,但仍然比 SelectMany 快 10 倍。【参考方案2】:

这是一个基于Andrew Bezzub 和fejesjoco's answers 的解决方案,预先分配所有需要的内存。这会产生 Θ(N) 内存使用量和 Θ(N) 时间(N 是总字节数)。

byte[] result = new byte[list.Sum(a => a.Length)];
using(var stream = new MemoryStream(result))

    foreach (byte[] bytes in list)
    
        stream.Write(bytes, 0, bytes.Length);
    

return result;

【讨论】:

【参考方案3】:

将它们全部写入 MemoryStream 而不是列表。然后调用 MemoryStream.ToArray()。或者当你有列表时,首先汇总所有字节数组长度,创建一个具有总长度的新字节数组,然后将每个数组复制到大数组中的最后一个之后。

【讨论】:

【参考方案4】:

使用 Linq:

    List<byte[]> list = new List<byte[]>();
    list.Add(new byte[]  1, 2, 3, 4 );
    list.Add(new byte[]  1, 2, 3, 4 );
    list.Add(new byte[]  1, 2, 3, 4 );

    IEnumerable<byte> result = Enumerable.Empty<byte>();

    foreach (byte[] bytes in list)
    
        result = result.Concat(bytes);
    

    byte[] newArray = result.ToArray();

也许更快的解决方案是(不预先声明数组):

IEnumerable<byte> bytesEnumerable = GetBytesFromList(list);

byte[] newArray = bytesEnumerable.ToArray();

private static IEnumerable<T> GetBytesFromList<T>(IEnumerable<IEnumerable<T>> list)

    foreach (IEnumerable<T> elements in list)
    
        foreach (T element in elements)
        
            yield return element;
        
    

上面似乎只会对每个数组进行一次迭代。

【讨论】:

注意这个解在字节数组的数量上是O(n^2)。 (你明白为什么吗?提示:序列运算符是惰性的。)你可以做得更好。你能找到一个字节数组数量呈线性的解吗? @Eric:谢谢!对我来说,解决方案是 O(n^2) 并不明显。如果我使用单独的方法来形成可枚举的字节怎么办?我更新了答案。 懒惰与它有什么关系?非惰性实现也是 O(N^2)。相反,你可以有一个惰性序列和 O(N) 性能。问题是所需的接口包装 - 您需要连续包装枚举器 - 您不能声明延续。这不是一个完全公平的比较,但在 Haskell 中,概念上相似的代码例如是惰性的 O(N)。 确实如此。 现在概括一下。 不一定是 List;它可能是 IEnumerable 因为您不使用 List 的任何属性,除了它是可枚举的。同样,它不一定是 byte[];您不使用字节或数组的属性。都是序列!在 C# 4 中,你可以说 IEnumerable Flatten(IEnumerable> items) foreach(var sequence in items) foreach(T t in sequence) yield return t;并完成它。 (这取决于 IEnumerable 的协方差,因此它仅适用于 C# 4;有一些方法可以使其在具有更多类型参数的 C# 3 中工作。) 当然你可以进一步推广到 IEnumerable Flatten(IEnumerable items, Func> selector) foreach(T item in items) foreach(R result in selector(item)) yield 返回结果; 嘿,你刚刚发明了 SelectMany;该方法已经在序列运算符库中。【参考方案5】:

您可以直接将它们添加到List&lt;byte&gt;,而不是将每个字节数组存储到List&lt;byte[]&gt; 中,对每个字节使用AddRange 方法。

【讨论】:

【参考方案6】:

嗯,list.addrange 怎么样?

【讨论】:

AddRange 是否将 List 转换为 byte[]?没有。

以上是关于连接字节 [] 的 C# 列表的主要内容,如果未能解决你的问题,请参考以下文章

c#将连接项的列表排序为多个列表

如何在 C# 中连接列表?

更正连接查询。 C# 和 MySQL

通过串行连接从智能卡读卡器接收数据(C#)

C#多线程聊天服务器,处理断开连接

C# 在流中添加 40 个字节