2016-01-11 79 views
1

我使用包含多個魔法字節序列的二進制格式。我想保留它們作爲不可變靜態成員的靜態類。將ReadOnlyCollection <byte>寫入流

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|m}ff 
    public static readonly IReadOnlyList<byte> DataDelimiter = Array.AsReadOnly(BitConverter.GetBytes(0L)); // eight binary zeroes, regardless of endianness 
} 

ReadOnlyCollection<byte>(從Array.AsReadOnly()返回)防止外部的代碼從改變值,不同於byte[]

但現在,我不能輸出通過stream.Write()Header,因爲它需要byte[]

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

是否有寫Header優雅的方式?或者我必須編寫一個循環,並逐個將字節輸入到流中?

+0

在[SO],MSDN上搜索並嘗試過像'stream.write readonlycollection byte'這樣的查詢,但沒有得到相關結果。 – Palec

+0

Stream需要'byte []'。點。您需要犧牲一些OOP概念或性能。這是你的選擇。 –

+1

我會封裝序列化/反序列化本身。考慮一個類與[靜態]方法無效WriteHeader(流),WriteDelimiter(流),ReadHeader(流),... – alexm

回答

4

只是使輸出數組不可改變

你可以考慮這樣的事情:

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|m}ff 
     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|m}ff 
    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); 
    } 
} 

這不會使數組不可變,而是私有的,這不是問題。

一個可能更好的解決方案:封裝霍夫曼流對象

您也可以實現自己的HuffmanStream這需要照顧的頭部的細節和其他方面對你的選擇!我實際上認爲這是理想的,因爲它會將霍夫曼流的所有問題都封裝在一個可測試的代碼中,而不是每個需要與之合作的地方都重複。

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,這是值得信賴的。

+0

編輯看起來不錯。我用不同的方法編寫了自己的答案,但是在實際的流中編寫HuffmanStream封裝可能是最終的解決方案。 [Huffman編碼](https://en.wikipedia.org/wiki/Huffman_coding)要求編寫比特序列不能平分爲字節,所以HuffmanStream也可以實現Write()來表示這些比特序列。 – Palec

+0

其實,「ReadOnlyCollection」,@usr總是需要誠信的假設。通過轉換,您可以從IReadOnlyList接口獲取它並使用它的['Items'屬性](https://msdn.microsoft.com/en-us/library/ms132509.aspx)只讀門面。我相信將底層數組傳遞給Stream的'Write'方法是可以的,因爲流沒有理由改變數組。當我閱讀[它的文檔](https://msdn.microsoft.com/en-us/library/system.io.stream.write.aspx)時,它實際上會違反合同。請糾正我,如果我錯了。 – Palec

+0

@Palec總是使用'someList.AsReadOnly()'來填充'IReadOnlyCollection'或'IReadOnlyList'。你*不能*修改,不需要誠意。另外,'Stream'是抽象的,所以'類MaliciousStream:Stream'不安全。 「Stream」的任何MS寫入的子類,如「FileStream」或「MemoryStream」都是安全的。 – ErikE

1

可以保留類,因爲它是和轉換Headerbyte[]的流

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|m}ff 
    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*方法將傳達該想法並且是發佈不可變陣列時的方法。


伊凡Stoev問題下評論說:

Stream需要byte[]。點。您需要犧牲一些OOP概念或性能。這是你的選擇。

原因是(我猜)字節數組直接傳遞給底層的系統調用,而其他集合具有不兼容的內部結構。因此,我相信,如果您想保持當前實現的HuffmanConsts,則不可能在每次調用時避開由新陣列分配引入的開銷。

+0

每天學習新東西。我在C#中居中,只是沒有克隆我的雷達,所以非常感謝!它比'Buffer.BlockCopy'好(這對於將部分目標複製到目標部分的能力仍然很有用,'Clone'顯然不會這麼做)。你介意我是否更新我的答案以使用'Clone'(並留下關於'BlockCopy'原始建議的評論,以便你的回答仍然有意義)? – ErikE

+0

是的,對我來說沒問題。 – Palec