C#如何以最小文件体积保存byte数组集合?
如何最小化固定长度字节数组集合的序列化体积
兄弟,你现在把每个byte转成两位ASCII字符的做法,相当于每个实际字节占用了2字节的存储空间,这就是文件体积过大的核心原因。咱们可以从几个层面优化,一步步把体积压到最小:
基础优化:直接写入原始二进制数据
这是最容易实现、效果也很明显的优化——别转字符串了,直接把字节数组以二进制形式写入文件,每个byte只占1字节,直接砍掉一半体积。
写入示例代码
using System.IO; public static void WritePatternsToBinaryFile(byte[][] patterns, string filePath) { // 先写入pattern的固定长度和总数量,反序列化时能准确拆分 int patternLength = patterns[0].Length; using (var writer = new BinaryWriter(File.Open(filePath, FileMode.Create))) { writer.Write(patternLength); writer.Write(patterns.Length); // 逐个写入每个pattern的原始字节 foreach (var pattern in patterns) { writer.Write(pattern); } } }
读取示例代码
using System.IO; public static byte[][] ReadPatternsFromBinaryFile(string filePath) { using (var reader = new BinaryReader(File.Open(filePath, FileMode.Open))) { int patternLength = reader.ReadInt32(); int patternCount = reader.ReadInt32(); var patterns = new byte[patternCount][]; for (int i = 0; i < patternCount; i++) { // 按固定长度读取每个pattern的字节 patterns[i] = reader.ReadBytes(patternLength); } return patterns; } }
用这种方式,50000个长度为12的pattern,体积是50000 * 12 = 600,000字节(约585KB),直接从1.7MB砍到一半多。
进阶优化:针对0-25取值的位压缩
你提到每个byte的取值范围是0-25,这意味着每个值只需要5位二进制就能表示(2^5=32,完全覆盖0-25)。我们可以把多个5位值打包到更少的字节里,进一步压缩体积:
比如每8个0-25的原始值,原本占8字节,现在只需要5字节(8*5=40位,刚好是5个8位字节),压缩比能到62.5%。
写入示例代码(位压缩)
using System.IO; public static void WritePatternsWithBitCompression(byte[][] patterns, string filePath) { int patternLength = patterns[0].Length; int totalValues = patterns.Length * patternLength; using (var writer = new BinaryWriter(File.Open(filePath, FileMode.Create))) { writer.Write(patternLength); writer.Write(patterns.Length); int bitBuffer = 0; int bitsInBuffer = 0; foreach (var pattern in patterns) { foreach (byte b in pattern) { // 先校验值的范围,确保是0-25 if (b > 25) throw new ArgumentOutOfRangeException(nameof(b), "每个pattern元素必须在0-25之间"); // 把当前值写入位缓冲区 bitBuffer |= (b << bitsInBuffer); bitsInBuffer += 5; // 缓冲区满8位就写入一个字节 while (bitsInBuffer >= 8) { writer.Write((byte)(bitBuffer & 0xFF)); bitBuffer >>= 8; bitsInBuffer -= 8; } } } // 写入缓冲区剩余的不足8位的内容 if (bitsInBuffer > 0) { writer.Write((byte)bitBuffer); } } }
读取示例代码(位压缩)
using System.IO; public static byte[][] ReadPatternsWithBitCompression(string filePath) { using (var reader = new BinaryReader(File.Open(filePath, FileMode.Open))) { int patternLength = reader.ReadInt32(); int patternCount = reader.ReadInt32(); int totalValues = patternCount * patternLength; var patterns = new byte[patternCount][]; for (int i = 0; i < patternCount; i++) { patterns[i] = new byte[patternLength]; } int bitBuffer = 0; int bitsInBuffer = 0; int valueIndex = 0; while (valueIndex < totalValues) { // 缓冲区不足5位时,读取新的字节补充 if (bitsInBuffer < 5) { int nextByte = reader.ReadByte(); bitBuffer |= (nextByte << bitsInBuffer); bitsInBuffer += 8; } // 提取5位的数值(0x1F是二进制5个1) byte value = (byte)(bitBuffer & 0x1F); patterns[valueIndex / patternLength][valueIndex % patternLength] = value; // 缓冲区右移5位,准备下一个值 bitBuffer >>= 5; bitsInBuffer -= 5; valueIndex++; } return patterns; } }
用这种位压缩方式,50000个长度为12的pattern(总共600,000个0-25的值),体积大概是375KB,比基础优化又小了近37%。
额外优化:重复pattern的去重
你提到集合里有重复的pattern(比如3个完全一样的),如果重复率高,还可以先对pattern去重,保存唯一的pattern列表,再用索引来标记每个位置的pattern,进一步缩小体积:
比如如果唯一pattern数量少于65536,用ushort(2字节)存索引,比存整个12字节的pattern省太多。
示例思路(去重+二进制存储)
// 需要自定义一个比较器来判断两个byte[]是否相等 public class ByteArrayComparer : IEqualityComparer<byte[]> { public bool Equals(byte[] x, byte[] y) { return StructuralComparisons.StructuralEqualityComparer.Equals(x, y); } public int GetHashCode(byte[] obj) { int hash = 17; foreach (byte b in obj) { hash = hash * 31 + b; } return hash; } } // 写入方法 public static void WritePatternsWithDeduplication(byte[][] patterns, string filePath) { var uniquePatterns = patterns.Distinct(new ByteArrayComparer()).ToList(); // 把每个原始pattern映射到它在唯一列表中的索引 var patternIndices = patterns.Select(p => (ushort)uniquePatterns.IndexOf(p)).ToArray(); using (var writer = new BinaryWriter(File.Open(filePath, FileMode.Create))) { writer.Write(patterns[0].Length); writer.Write(uniquePatterns.Count); writer.Write(patterns.Length); // 写入所有唯一的pattern foreach (var pattern in uniquePatterns) { writer.Write(pattern); } // 写入索引数组 foreach (var index in patternIndices) { writer.Write(index); } } } // 读取方法 public static byte[][] ReadPatternsWithDeduplication(string filePath) { using (var reader = new BinaryReader(File.Open(filePath, FileMode.Open))) { int patternLength = reader.ReadInt32(); int uniqueCount = reader.ReadInt32(); int totalCount = reader.ReadInt32(); // 读取所有唯一的pattern var uniquePatterns = new List<byte[]>(); for (int i = 0; i < uniqueCount; i++) { uniquePatterns.Add(reader.ReadBytes(patternLength)); } // 读取索引并还原原始集合 var patterns = new byte[totalCount][]; for (int i = 0; i < totalCount; i++) { ushort index = reader.ReadUInt16(); patterns[i] = uniquePatterns[index]; } return patterns; } }
如果你的集合重复率很高(比如一半以上都是重复的),这种方式的体积会比之前的优化还要小很多。
内容的提问来源于stack exchange,提问作者jnt
相关产品推荐
相关产品推荐

