【问题标题】:Storing String Indexed Binary Data in File Using C# [closed]使用 C# 在文件中存储字符串索引的二进制数据 [关闭]
【发布时间】:2019-12-30 22:05:35
【问题描述】:

我想知道将由字符串键索引的二进制数据存储到单个文件中的最佳方法是什么。

这就是我要寻找的情况:

  • 由可变长度的字符串键索引的数据(最多 255 个字符,仅 ASCII 即可)。
  • 二进制数据具有可变长度(500 字节,最大 10 KB)。
  • 存储的数据量
  • 在生产中只需要“GetDataByKey”和“GetAllKeys”函数,因此应该很快。
  • 生产中不使用添加数据,因此可能会很慢。

是否有任何基于 C# 的简单库可以满足这些要求?

我正在查看一些 NoSQL 数据库,但对于如此简单的数据结构来说,这似乎有点过头了。

由于在应用程序运行期间只使用了一小部分数据记录,我不希望在应用程序启动时将所有内容都读入内存(例如使用序列化),而是只从文件中读取真正需要的条目在运行时。

任何想法或提示将不胜感激,谢谢!

【问题讨论】:

    标签: c# database file binary


    【解决方案1】:

    使用 Binaryformater 如下代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Xml.Serialization;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            const string FILENAME = @"c:\temp\test.bin";
            static void Main(string[] args)
            {
                Read_Write readWrite = new Read_Write();
                readWrite.CreateData(1000);
                readWrite.WriteData(FILENAME);
                Data data = readWrite.GetRecord(FILENAME, "101");
            }
        }
        [Serializable()]
        [XmlRoot(ElementName="ABC")]
        public struct Data
        {
            public byte[] name;
            public byte[] data;
        }
    
        public class Read_Write
        {
            [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
            static extern int memcmp(byte[] b1, byte[] b2, long count);
    
    
            const int MIN_SIZE = 500;
            const int MAX_SIZE = 10000;
            public List<Data> data { get; set; }
            Dictionary<string, Data> dict = new Dictionary<string, Data>();
    
    
            public void CreateData(int numberRecords)
            {
                data = new List<Data>();
                for (int i = 0; i < numberRecords; i++)
                {
                    Data newData = new Data();
    
                    string name = i.ToString() + '\0'; //null terminate string
                    newData.name = Encoding.UTF8.GetBytes(name);
    
                    Random rand = new Random();
    
                    int size = rand.Next(MIN_SIZE, MAX_SIZE);
                    newData.data = Enumerable.Range(0, size).Select(x => (byte)(rand.Next(0, 0xFF) & 0xFF)).ToArray();
    
                    data.Add(newData);
                }
            }
            public void WriteData(string filename)
            {
                Stream writer = File.OpenWrite(filename);
                //write number of records
                byte[] numberOfRecords = BitConverter.GetBytes((int)data.Count());
                writer.Write(numberOfRecords, 0, 4);
                foreach (Data d in data)
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    formatter.Serialize(writer, d);
                }
    
                writer.Flush();
                writer.Close();
            }
            public Data GetRecord(string filename, string name)
            {
                Data record = new Data();
                Stream reader = File.OpenRead(filename);
                byte[] numberOfRecords = new byte[4];
                reader.Read(numberOfRecords, 0, 4);
                int records = BitConverter.ToInt32(numberOfRecords, 0);
    
                DateTime start = DateTime.Now;
                for(int i = 0; i < records; i++)
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    Data d = (Data)formatter.Deserialize(reader);
                    //if (name == GetString(d.name))
                    //{
                    //    record = d;
                    //    break;
                    //}
    
                }
                DateTime end  = DateTime.Now;
                TimeSpan time = end - start;
    
                reader.Close();
                return record;
            }
    
            public string GetString(byte[] characters)
            {
                int length = characters.ToList().IndexOf(0x00);
                return Encoding.UTF8.GetString(characters, 0, length);
            }
        }
    }
    

    【讨论】:

    • 首先感谢您的快速回复,jdweng!当然,序列化会起作用,但是所有的东西都会一直保存在内存中。由于不需要经常使用数据(尤其不是所有记录),我宁愿只读取我需要的二进制数据(基于字符串键),而不是一直将所有内容保存在内存中。考虑到记录的大小,它并没有那么多内存,但如果整个应用程序运行中只需要 1% 的记录,它仍然会感到浪费。
    • 构建上下文表,当您写入文件时,整个数据将在内存中。
    • 正如我在问题中提到的那样,在生产使用中不会完成写入文件:所以这基本上只是开发应用程序时的一次性任务。在生产使用时,数据是只读的(仅需要“GetDataByKey”和“GetAllKeys”函数)。这就是为什么我认为在每个程序启动时将整个数据集合加载到内存中并不是一个很好的解决方案,而实际上只需要访问一小部分数据。
    • 昨天当我尝试一次序列化一个 Data 对象然后一次反序列化一个时,序列化出现了问题。该解决方案需要获取文件中每个 Data 对象的偏移量。我正在考虑使用 Marshal 方法而不是序列化。如果我有时间,我会尝试让它工作。在 c++ 中,让它工作起来要容易得多。
    • 我更新了代码以读取一条记录。没有创建目录。只需阅读直到名称匹配。我在文件中的第一项写了记录数。在读取每条记录之前读取计数。作为测试的一部分,我验证了每条反序列化的记录都与原始数据匹配。
    【解决方案2】:

    由于似乎还没有可用的解决方案/库(可能是因为问题太简单了,无法分享 ;-)),我自己建立了一个小班。

    如果其他人需要相同的,这就是我现在存储这个基于字符串键的二进制数据的方式:

    internal class BinaryKeyStorage
    {
        private const string FILE_PATH = @"data.bin";
    
        private static MemoryMappedFile _memoryFile;
        private static MemoryMappedViewStream _memoryFileStream;
        private static Dictionary<string, Entry> _index;
    
        private class Entry
        {
            public Entry(int position, int length)
            {
                Position = position;
                Length = length;
            }
    
            public int Position { get; }
            public int Length { get; }
        }
    
        public static void CreateFile(Dictionary<string, byte[]> keyValues)
        {
            // 4 bytes for int count of entries
            // and per entry:
            // - string length + 1 byte for string prefix
            // - 2x4 bytes for int address start and length
            var headerLength = 4 + keyValues.Keys.Sum(dataKey => dataKey.Length + 9);
    
            var nextStartPosition = headerLength;
            using (var binaryWriter = new BinaryWriter(File.Open(FILE_PATH, FileMode.Create)))
            {
                binaryWriter.Write(keyValues.Count);
    
                // writing header
                foreach (var keyValue in keyValues)
                {
                    binaryWriter.Write(keyValue.Key);
                    binaryWriter.Write(nextStartPosition);
                    binaryWriter.Write(keyValue.Value.Length);
    
                    nextStartPosition += keyValue.Value.Length;
                }
    
                // writing data
                foreach (var keyValue in keyValues)
                {
                    binaryWriter.Write(keyValue.Value);
                }
            }
        }
    
        public static List<string> GetAllKeys()
        {
            InitializeIndexIfNeeded();
    
            return _index.Keys.ToList();
        }
    
        public static byte[] GetData(string key)
        {
            InitializeIndexIfNeeded();
    
            var entry = _index[key];
            _memoryFileStream.Seek(entry.Position, SeekOrigin.Begin);
    
            var data = new byte[entry.Length];
            _memoryFileStream.Read(data, 0, data.Length);
    
            return data;
        }
    
        private static void InitializeIndexIfNeeded()
        {
            if (_memoryFile != null) return;
    
            _memoryFile = MemoryMappedFile.CreateFromFile(FILE_PATH, FileMode.Open);
            _memoryFileStream = _memoryFile.CreateViewStream();
    
            _index = new Dictionary<string, Entry>();
            using (var binaryReader = new BinaryReader(_memoryFileStream, Encoding.Default, true))
            {
                var count = binaryReader.ReadInt32();
                for (var i = 0; i < count; i++)
                {
                    var dataKey = binaryReader.ReadString();
                    var dataPosition = binaryReader.ReadInt32();
                    var dataLength = binaryReader.ReadInt32();
    
                    _index.Add(dataKey, new Entry(dataPosition, dataLength));
                }
            }
        }
    }
    

    它只是将文件头/索引(字符串键以及数据的位置/长度)缓存在内存中,只有在需要时才直接从内存映射文件中读取实际数据。

    【讨论】:

      猜你喜欢
      • 2011-09-29
      • 1970-01-01
      • 2021-11-25
      • 1970-01-01
      • 1970-01-01
      • 2011-06-19
      • 1970-01-01
      • 2013-01-13
      • 1970-01-01
      相关资源
      最近更新 更多