【问题标题】:C# - How to save byte values to file with smallest size possible?C# - 如何将字节值保存到尽可能小的文件中?
【发布时间】:2023-03-07 16:51:01
【问题描述】:

我需要以尽可能小的文件大小序列化以下数据。

我有一组模式,每个模式都是一个设定长度的字节数组 (byte[])。

在本例中,让我们使用 5 的模式长度,因此字节数组将是:

var pattern = new byte[] {1, 2, 3, 4, 5};

假设我们在一个集合中有 3 个相同的模式:

var collection = new byte[][] { pattern, pattern, pattern };

目前我正在将集合保存在 ASCII 编码文件中。使用上面的集合,保存的文件将如下所示:

010203040501020304050102030405

数组中的每个字节都由 2 位数字 (00) 表示,这样我就可以满足 0 到 25 的字节值,可以像这样可视化:

[01|02|03|04|05] [01|02|03|04|05] [01|02|03|04|05]

当我反序列化文件时,我将每个 2 个字符的块解析为一个字节,并将每 5 个字节放入一个字节数组中。

据我了解,ASCII 编码文件中的每个字符都是一个字节 - 提供可能的 256 个不同的值,但我需要的只是每个 2 个字符块是从 0 到 25 的可能十进制值。

当我保存一个包含 50,000 个模式的文件时,每个模式的长度为 12,我最终得到一个 1.7MB 的文件,这太大了。

我可以在 C# 中使用什么编码来使我的文件更小?

请提供如何在文件中写入和读取此数据的示例代码。

【问题讨论】:

  • 如果您的文件不必是 ASCII,您可以使用 ZipArchiveZipFile 来压缩数据。
  • 文件必须可以被文本编辑器读取吗?你可以只保存字节而不是数字来保存一半的数据。
  • 就像一般问题一样,gzip 流是不可能的?也没有冒犯,但这听起来很像家庭作业。
  • 0-25 -> A .. Z. 或使用每 5 位表示一个值的比特流。使用进行压缩/解压缩的流可能更容易。
  • 通过二进制写入,您最多可以压缩 5 位/数字 (log2(26) = 4.7)...但是写入基于位的信息很痛苦

标签: c# arrays serialization


【解决方案1】:

在将二进制数据编码为条形码时,我做了类似的事情(请参阅Efficient compression and representation of key value pairs to be read from 1D barcodes)。考虑以下代码,它将样本序列化到文件中并立即反序列化它们:

static void Main(string[] args)
{
    var data = new List<byte[]>() {
        new byte[] { 01, 05, 15, 04, 11, 00, 01, 01, 05, 15, 04, 11, 00, 01 },
        new byte[] { 09, 04, 02, 00, 08, 12, 01, 07, 04, 02, 00, 08, 12, 01 },
        new byte[] { 01, 05, 06, 04, 02, 00, 01, 01, 05, 06, 04, 02, 00, 01 }
    };

    // has to be known when loading the file
    var reasonableBase = data.SelectMany(i => i).Max() + 1;

    using (var target = File.OpenWrite("data.bin"))
    {
        using (var writer = new BinaryWriter(target))
        {
            // write the number of lines (16 bit, lines limited to 65536)
            writer.Write((ushort)data.Count);

            // write the base (8 bit, base limited to 255)
            writer.Write((byte)reasonableBase);

            foreach (var sample in data)
            {
                // converts the byte array into a large number of the known base (bypasses all the bit-mess)
                var serializedData = ByteArrayToNumberBased(sample, reasonableBase).ToByteArray();

                // write the length of the sample (8 bit, limited to 255)
                writer.Write((byte)serializedData.Length);
                writer.Write(serializedData);
            }
        }
    }

    var deserializedData = new List<byte[]>();

    using (var source = File.OpenRead("data.bin"))
    {
        using (var reader = new BinaryReader(source))
        {
            var lines = reader.ReadUInt16();
            var sourceBase = reader.ReadByte();

            for (int i = 0; i < lines; i++)
            {
                var length = reader.ReadByte();
                var value = new BigInteger(reader.ReadBytes(length));

                // chunk the bytes back of the big number we loaded
                // works because we know the base
                deserializedData.Add(NumberToByteArrayBased(value, sourceBase));
            }
        }
    }
}

private static BigInteger ByteArrayToNumberBased(byte[] data, int numBase)
{
    var result = BigInteger.Zero;

    for (int i = 0; i < data.Length; i++)
    {
        result += data[i] * BigInteger.Pow(numBase, i);
    }

    return result;
}

private static byte[] NumberToByteArrayBased(BigInteger data, int numBase)
{
    var list = new List<Byte>();

    do
    {
        list.Add((byte)(data % numBase));
    }
    while ((data = (data / numBase)) > 0);

    return list.ToArray();
}

与您的格式相比,示例数据将序列化为 27 个字节而不是 90 个。使用 @xanatos 的每个符号 4.7 位,完美的结果将是 14 * 3 * 4.7 / 8 = 24,675 bytes,所以这还不错(公平地说:示例序列化为30 字节,基数设置为 26)。

【讨论】:

  • 非常感谢您的回复,我得花点时间查看一下
【解决方案2】:

下面是一个示例,说明如何使用 GZipStreamBinaryFormatter 从压缩文件中读取和写入数据。

对于小型阵列来说效率不是很高,但对于大型阵列来说效率更高。但是,请注意,这依赖于可压缩的数据 - 如果不是,那么这将没有任何用处!

using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;

namespace Demo
{
    static class Program
    {
        static void Main()
        {
            var pattern    = new byte[] { 1, 2, 3, 4, 5 };
            var collection = new [] { pattern, pattern, pattern };

            string filename = @"e:\tmp\test.bin";
            zipToFile(filename, collection);

            var deserialised = unzipFromFile(filename);

            Console.WriteLine(string.Join("\n", deserialised.Select(row => string.Join(", ", row))));
        }

        static void zipToFile(string file, byte[][] data)
        {
            using (var output = new FileStream(file, FileMode.Create))
            using (var gzip   = new GZipStream(output, CompressionLevel.Optimal))
            {
                new BinaryFormatter().Serialize(gzip, data);
            }
        }

        static byte[][] unzipFromFile(string file)
        {
            using (var input = new FileStream(file, FileMode.Open))
            using (var gzip  = new GZipStream(input, CompressionMode.Decompress))
            {
                return (byte[][]) new BinaryFormatter().Deserialize(gzip);
            }
        }
    }
}

【讨论】:

  • 非常感谢,会试一试
【解决方案3】:

有时简单是最好的妥协。

可以将矩形阵列视为一系列线性阵列。

字节文件是字节的线性数组。

这是一个非常简单的代码,用于转换一个矩形字节数组并将字节写入文件:

// All patterns must be the same length so they can be split when reading
File.WriteAllBytes(Path.GetTempFileName(), collection.SelectMany(p => p).ToArray()); 

System.Linq.Enumerable.SelectMany(pattern =&gt; pattern) 获取一个序列序列并将它们展平为一个序列。 (它与 ToArray() 一起不是最有效的,但对于 50,000 * 4 个元素,它可能没问题。)

考虑到作为起点,如果需要压缩,Zip 将是一种可行的方法,如shown by Matthew Watson

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-06
    • 2011-10-21
    • 1970-01-01
    • 2022-01-20
    • 2021-05-25
    相关资源
    最近更新 更多