将 ReadOnlyCollection<byte> 写入流

Posted

技术标签:

【中文标题】将 ReadOnlyCollection<byte> 写入流【英文标题】:Writing ReadOnlyCollection<byte> to stream 【发布时间】:2016-04-16 03:12:11 【问题描述】:

我使用包含多个魔术字节序列的二进制格式。我想将它们作为不可变的静态成员保存在静态类中。

public static class HuffmanConsts

    // output format: Header, serialized tree (prefix), DataDelimiter, coded data (logical blocks are 8 byte large, Little Endian)
    public const string Extension = ".huff";
    public static readonly IReadOnlyList<byte> Header = Array.AsReadOnly(new byte[] 0x7B, 0x68, 0x75, 0x7C, 0x6D, 0x7D, 0x66, 0x66); // string hu|mff
    public static readonly IReadOnlyList<byte> DataDelimiter = Array.AsReadOnly(BitConverter.GetBytes(0L)); // eight binary zeroes, regardless of endianness

ReadOnlyCollection&lt;byte&gt;(从Array.AsReadOnly() 返回)防止外部代码更改值,这与byte[] 不同。

但是现在,我无法通过stream.Write() 输出Header,因为它需要byte[]

stream.Write(HuffmanConsts.Header, 0, HuffmanConsts.Header.Count)

有没有一种优雅的方式来编写Header?还是我必须编写一个循环并将字节一个一个地输入到流中?

【问题讨论】:

在Stack Overflow、MSDN 上搜索并尝试了stream.write readonlycollection byte 之类的查询,但没有得到相关结果。 Stream 需要byte[]。点。您需要牺牲一些 OOP 概念或性能。选择权在你。 我会封装序列化/反序列化本身。考虑一个具有 [静态] 方法的类 void WriteHeader(stream), WriteDelimiter(stream), ReadHeader(stream), ... 【参考方案1】:

只是让输出数组不可变

你可以考虑这样的事情:

public static class HuffmanConsts 
   // output format: Header, serialized tree (prefix), DataDelimiter,
   // coded data (logical blocks are 8 byte large, Little Endian)
   public const string Extension = ".huff";

   private static readonly IReadOnlyList<byte> _header =
      // string hu|mff
      Array.AsReadOnly(new byte[] 0x7B, 0x68, 0x75, 0x7C, 0x6D, 0x7D, 0x66, 0x66);
   private static readonly IReadOnlyList<byte> _dataDelimiter =
      // eight binary zeroes, regardless of endianness
      Array.AsReadOnly(BitConverter.GetBytes(0L)); 

   public static byte[] Header  get  return _header.ToArray();  
   public static byte[] DataDelimiter  get  return _dataDelimiter.ToArray();  

处理 ToArray 的任何性能影响

但是,每次访问这些属性时都会产生ToArray() 的开销。为了减轻潜在的性能损失(注意:测试是为了看看是否真的有一个!),你可以使用System.Buffer.BlockCopy

private static readonly byte[] _header =
   // string hu|mff
   new byte[] 0x7B, 0x68, 0x75, 0x7C, 0x6D, 0x7D, 0x66, 0x66;
private static int BYTE_SIZE = 1;
private static byte[] GetHeaderClone() 
   byte[] clone = new byte[_header.Length];
   Buffer.BlockCopy(_header, 0, clone, 0, _header.Length * BYTE_SIZE);
   return clone;

更好的解决方案:封装写入流

您还可以创建扩展方法,让您的消费者不再纠结于自己编写这些流组件的细节,例如,WriteHeader 方法可能如下所示:

public static class StreamExtensions 
   // include BlockCopy code from above
   public static void WriteHuffmanHeader(this Stream stream) 
      var header = GetHeaderClone();
      stream.Write(header, 0, header.Length);
   

这不会使数组不可变,但私有化不是问题。

可能更好的解决方案:封装 Huffman 流对象

您还可以选择实现自己的HuffmanStream,它会为您处理标题和其他方面的细节!我实际上认为这是理想的,因为它将 Huffman 流的所有关注点封装成一段可测试的代码,不会在你需要使用的每个地方都重复。

public class HuffmanStream : Stream 
   private Stream _stream = new MemoryStream();
   private static byte[] _header = ... ;
   public HuffmanStream( ... ) 
      ...
      _stream.Write(_header, 0, _header.Length)
      // the stream already has the header written at instantiation time
   


注意:将byte[] 实例传递给Stream.Write() 时,可能会在方法返回后对其进行修改,因为该方法可以直接访问数组。行为良好的 Stream 实现不会这样做,但为了防止自定义流,您必须Stream 实例视为敌对,因此永远不要向它们传递对不应该的数组的引用不能改变。例如,任何时候你想将_header 字节数组传递给possiblyHostileStream.Write(),你需要传递_header.Clone()。我的HuffmanStream 不需要这个,因为它使用了可以信任的MemoryStream

【讨论】:

编辑看起来很棒。我用不同的方法编写了自己的答案,但是围绕实际流编写 HuffmanStream 包装器可能是最终的解决方案。 Huffman coding 需要写入不能均匀划分为字节的位序列,因此 HuffmanStream 也可以实现 Write() 来表示这些位序列。 实际上,ReadOnlyCollection、@usr 始终需要善意假设。通过强制转换,您可以从IReadOnlyList 接口获取它并使用它的Items property,然后您就进入了只读外观。我相信将底层数组传递给StreamWrite 方法是可以的,因为流没有理由改变数组。当我读到its documentation 时,这实际上是违反合同的。如果我错了,请纠正我。 @Palec 始终使用someList.AsReadOnly() 来填充IReadOnlyCollectionIReadOnlyList。你不能修改它,不需要善意。另外,Stream 是抽象的,所以class MaliciousStream : Stream 不安全。 Stream 的任何 MS 编写的子类,例如 FileStreamMemoryStream 都是安全的。 啊啊,Items property is protected!这就是我错过的。 @Palec 它可能会违反合同,真的。这里的相关场景是生活在同一个 .NET 进程中的多个不合作方。这就是为什么 BCL 如此认真地不允许这样做。如果一个库运行起来会导致其他组件出现可怕的错误。如果您控制所有这些代码,我不会打扰任何这些,只需公开数组。或者可以使用此问题中找到的 99% 保护方法之一。【参考方案2】:

您可以保持原样并Header 转换为byte[] 用于流

stream.Write(HuffmanConsts.Header.ToArray(), 0, HuffmanConsts.Header.Count)

这个IEnumerable.ToArray()扩展方法来自System.Linq

或者,您可以直接存储字节数组并使用属性返回其克隆。这是first approach described by ErikE 的一个更简单的变体。不再需要ReadOnlyCollection

public static class HuffmanConsts

    // output format: Header, serialized tree (prefix), DataDelimiter, coded data (logical blocks are 8 byte large, Little Endian)
    public const string Extension = ".huff";
    private static byte[] _header = new byte[] 0x7B, 0x68, 0x75, 0x7C, 0x6D, 0x7D, 0x66, 0x66; // string hu|mff
    private static byte[] _dataDelimiter = BitConverter.GetBytes(0L); // eight binary zeroes, regardless of endianity
    public byte[] Header  get  return (byte[])_header.Clone();  
    public byte[] DataDelimiter  get  return (byte[])_dataDelimiter.Clone();  

我不赞成这个解决方案,因为这些属性做了大量的工作(分配;尽管仍然是 O(1))。根据Framework Design Guidelines,将它们转换为Get* 方法可以传达这个想法,并且是发布不可变数组时要走的路。


正如 Ivan Stoev 在问题下评论的那样:

Stream 需要 byte[]。点。您需要牺牲一些 OOP 概念或性能。选择权在你。

原因是(我猜)字节数组直接传递给底层系统调用,而其他集合具有不兼容的内部结构。因此,我相信,如果您想保留HuffmanConsts 的当前实现,就不可能避免每次调用新数组分配带来的开销。

【讨论】:

每天学习新东西。我在我的 C# 中处于中间状态,只是没有在我的雷达上看到 Clone,所以谢谢你!它比Buffer.BlockCopy 好(它仍然可以将部分目标复制到部分目的地,Clone 显然不会这样做)。您是否介意我更新我的答案以使用Clone(并将BlockCopy 的原始推荐留在cmets 中,这样您的答案仍然有意义)? 是的,我没问题。

以上是关于将 ReadOnlyCollection<byte> 写入流的主要内容,如果未能解决你的问题,请参考以下文章

将 Dictionary<TKey, List<TValue>> 转换为 ReadOnlyDictionary<TKey, ReadOnlyCollection<T

根据 FxCop,为啥 ReadOnlyCollection<ReadOnlyCollection<T>> 不好,以及在生成不可变二维对象时有啥替代方法? [复制]

ReadOnlyCollection 或 IEnumerable 用于公开成员集合?

更新 GridView 中的 ReadOnlyCollection

如何在 C# 中使用带有 HashSet<ReadOnlyCollection<string>> 类型的 exceptWith?

ReadonlyCollection,对象是不可变的吗?