Files
mir_server/sdk/utils/nonuse/CustomFileDB.h

274 lines
14 KiB
C
Raw Permalink Normal View History

2025-01-09 17:45:40 +08:00
#pragma once
/************************************************************************
K-V数据库系统
FileDB是一个简易的面向二进制的KV数据存储系统
Chunk
FileDB数据库中存储了100份数据100
使
24DataHeader结构ID以及块存储的数据长度
ID
FileDB中存储数据以及获取数据的一句是64位的整数IDID必须是唯一
1ID向数据库存储了100字节的数据50ID值1获
500ID是不允许使用的FileDB的实现中有着特殊的
使
************************************************************************/
#ifdef WIN32
class CCustomFileDB
{
public:
CCustomFileDB();
~CCustomFileDB();
//打开数据库
bool open(LPCTSTR sDBName);
//关闭索引文件
void close();
//创建数据库
bool create(LPCTSTR sDBName);
//向数据库更新数据,如果数据不存在则添加为新数据
bool put(INT64 nDataId, LPCVOID lpData, INT64 dwSize);
//从数据库中读取数据
//如果dwBufferSize为0则进行指定ID数据的长度查询操作此时函数返回此ID的数据长度。
//如果dwBufferSize为正数值则函数向lpBuffer中拷贝数据库中存储的数据拷贝的长度
//受dwBufferSize限制。如果dwBufferSize大于数据的实际长度则仅向lpBuffer中拷贝
//数据的实际长度字节数。函数返回值表示最终向lpBuffer拷贝了多少字节。
INT64 get(INT64 nDataId, LPVOID lpBuffer, INT64 dwBufferSize) const;
//从数据库移除数据
//如果数据存在且删除成功则返回true。
//改操作可能引发索引列表的内存大量拷贝。
bool remove(INT64 nDataId);
//获取存储在数据库中的第nIndex个数据的ID
inline INT64 getId(INT_PTR nIndex){ return m_DataList[nIndex].pIndex->chunk.nId; }
//获取数据块单位长度
inline DWORD getChunkSize() const{ return m_Header.dwChunkSize; }
//设置数据块单位长度
inline void setChunkSize(DWORD dwNewSize){ m_Header.dwChunkSize = dwNewSize; };
//是否在向文件写入数据后立即提交数据,如果开启则每当发生一次完整的数据和索引文件的写操作时,
//都会等待文件被写入到硬盘后才返回。这个可以有效的提高数据存储的安全性。但由于同步文件写操作
//比较耗时,因此可能因为存储效率被降低而造成性能瓶颈。
inline bool getFlushFileDataImmediately() const{ return m_boFlushFileDataImmediately != 0; }
inline void setFlushFileDataImmediately(bool boImmediately){ m_boFlushFileDataImmediately = (boImmediately ? TRUE : FALSE); }
//获取数据记录数量
inline INT_PTR getDataCount() const{ return m_DataList.count(); }
//获取空闲数据块数量
inline INT_PTR getFreeChunkCount() const{ return m_FreeDataOffsetList.count(); }
//获取空索引数量
inline INT_PTR getNullIndexCount() const{ return m_NullIndexList.count(); }
//计算总存储数据大小
//通过计算总存储数据大小与数据文件大小,可以进行数据文件利用率的计算:利用率 = 总存储数据大小 / 数据文件大小
INT64 getTotalDataSize() const;
//计算总空闲数据块大小
//通过计算总空闲数据块大小与数据文件大小,可以进行数据空闲率的计算:空闲率 = 总空闲数据块大小 / 数据文件大小
INT64 getTotalFreeChunkSize() const;
//获取数据文件大小
INT64 getDataFileSize() const;
//获取数据ID列表
//函数从第nIndex个ID开始填充pIdList列表并且最多填充nMaxCount个。
//函数返回值表示实际向pIdList中填充的ID数量返回值可能比nMaxCount小。
INT64 getIdList(INT64 nIndex, PINT64 pIdList, INT64 nMaxCount);
public:
static const TCHAR DataFileExt[];//数据文件后缀
static const TCHAR IndexFileExt[];//索引文件后缀
/** 数据库文件头 **/
struct FDBHeader
{
static const DWORD IDENT = MAKEFOURCC('F', 'D', 'I', 0);
static const DWORD VERSION = 0x010B0A0D;
//文件标识固定为MAKEFOURCC('F', 'D', 'B', 0)
DWORD dwIdent;
//数据库文件格式版本号目前为0x010B0A0D
DWORD dwVersion;
//数据库中存储的记录数量
INT nRecordCount;
/*数据记录块单位大小
便
dwChunkSize的倍数1024
800
102410252048
dwChunkSize在数据库创建的时候既被指定
*/
DWORD dwChunkSize;
//保留字节
char btReseves[48];
};
#pragma pack(push, 1)
//数据记录头。每个数据块均以一个数据记录头开始。
struct DataHeader
{
static const INT64 VALID_IDENT = 0xFFAADD8800DDAAFF;
static const INT64 INVALID_IDENT = 0xCCAADD8800DDAACC;
INT64 dwIdent; //数据记录头标志固定为0xFFAADD8800DDAAFF
INT64 nId; //数据ID
INT64 nDataSize; //数据长度(不含本记录头)
public:
DataHeader(){ dwIdent = VALID_IDENT; }
};
#pragma pack(pop)
// 数据块索引
struct ChunkIndex
{
#pragma pack(push, 1)
struct ChunkDesc
{
INT64 nId; //数据记录唯一ID值。如果值为零则表示该记录为一个空闲的数据块可以被回收利用。
INT64 nDataOffset;//记录在数据库文件中的绝对偏移值
INT64 nDataSize; //数据记录字节数包含数据块开头的DataHeader的大小
INT64 nChunkSize; //数据记录块大小。如果值为0则表示该记录为没有任何意义该索引在文件中的位置空间可以被回收利用。
}chunk;
#pragma pack(pop)
INT64 nIndexOffset; //本索引记录在索引文件中的偏移位置
};
//有效数据记录项
struct AvaliableDataIndex
{
ChunkIndex *pIndex;
public:
inline operator ChunkIndex* (){ return pIndex; }
inline INT_PTR compare (const AvaliableDataIndex & another) const
{
if (pIndex == another.pIndex) return 0;
if (this->pIndex->chunk.nId < another.pIndex->chunk.nId) return -1;
else if (this->pIndex->chunk.nId > another.pIndex->chunk.nId) return 1;
//如果出现相同ID索引的情况那么就是发生错误了
else { Assert(pIndex == another.pIndex); return 0; };
}
inline INT_PTR compareKey(const INT64 nID) const
{
if (this->pIndex->chunk.nId < nID) return -1;
else if (this->pIndex->chunk.nId > nID) return 1;
else return 0;
}
};
//空闲记录块大小排序项
struct FreeDataSizeIndex
{
ChunkIndex *pIndex;
public:
inline operator ChunkIndex* (){ return pIndex; }
inline bool operator == (const FreeDataSizeIndex & another) const { return pIndex == another.pIndex; }
inline INT_PTR compare (const FreeDataSizeIndex & another) const
{
//if (pRecord == another.pRecord) return 0;
if (this->pIndex->chunk.nChunkSize < another.pIndex->chunk.nChunkSize) return -1;
else if (this->pIndex->chunk.nChunkSize > another.pIndex->chunk.nChunkSize) return 1;
else return 0;
}
inline INT_PTR compareKey(const INT64 nSize) const
{
if (this->pIndex->chunk.nChunkSize < nSize) return -1;
else if (this->pIndex->chunk.nChunkSize > nSize) return 1;
else return 0;
}
};
// 空闲记录块偏移排序项
struct FreeDataOffsetIndex
{
ChunkIndex *pIndex;
public:
inline operator ChunkIndex* (){ return pIndex; }
inline bool operator == (const FreeDataOffsetIndex & another) const { return pIndex == another.pIndex; }
inline INT_PTR compare (const FreeDataOffsetIndex & another) const
{
if (this->pIndex->chunk.nDataOffset < another.pIndex->chunk.nDataOffset) return -1;
else if (this->pIndex->chunk.nDataOffset > another.pIndex->chunk.nDataOffset) return 1;
//对于存储数据块索引的列表,如果出现相同偏移位置的索引的情况,那么就是发生错误了!
else { Assert(pIndex == another.pIndex); return 0; };
}
inline INT_PTR compareKey(const INT64 nOffset) const
{
if (this->pIndex->chunk.nDataOffset < nOffset) return -1;
else if (this->pIndex->chunk.nDataOffset > nOffset) return 1;
else return 0;
}
};
typedef CObjectAllocator<ChunkIndex> IndexAllocator;
typedef wylib::container::CBaseList<ChunkIndex*> IndexRecordList;
typedef CCustomSortList<AvaliableDataIndex, INT64> DataList;
typedef CCustomSortList<FreeDataSizeIndex, INT64> FreeDataSizeList;
typedef CCustomSortList<FreeDataOffsetIndex, INT64> FreeDataOffsetList;
typedef wylib::container::CBaseList<ChunkIndex*> NullIndexList;
typedef wylib::sync::lock::CCSLock FileLock;
protected:
HANDLE m_hIndexFile; //索引文件句柄
HANDLE m_hDataFile; //数据文件句柄
FileLock m_IndexFileLock;//索引文件锁
FileLock m_DataFileLock; //数据文件锁
FDBHeader m_Header; //文件头
IndexAllocator m_IndexAllocator; //索引记录申请器
IndexRecordList m_IndexRecordList; //索引记录列表
DataList m_DataList; //有效数据索引列表
FreeDataSizeList m_FreeDataSizeList; //空闲数据大小排序表,用于快速找到一个合适的空数据位置
FreeDataOffsetList m_FreeDataOffsetList; //空闲数据偏移排序表,用于合并连续的空数据位置
NullIndexList m_NullIndexList; //无效索引记录列表
INT64 m_nNextIndexOffset; //下一个索引记录的偏移位置
BOOL m_boFlushFileDataImmediately;//是否在向文件写入数据后立即提交数据
protected:
//申请一个索引记录。返回的索引中会正确填充所以记录的所有成员。
//当dwDataSize为0时表示仅申请一个索引对象不预留空间。
ChunkIndex* allocChunk(INT64 nId, INT64 dwDataSize);
//将索引记录写入到索引文件中
bool flushIndex(ChunkIndex *pIndex);
//将索引记录添加为空数据索引
void addFreeChunk(ChunkIndex *pIndex);
//将一个大的数据块拆分成两个小块参数dwSplitOutSize为从大数据块中拆分出来的新数据块的大小
bool splitLargeChunk(ChunkIndex *pIndex, INT64 dwSplitOutSize);
//重新申请一个更大的数据库块
ChunkIndex* reallocChunk(ChunkIndex *pIndex, INT64 dwNewDataSize);
//向数据文件写数据块
bool writeData(ChunkIndex *pIndex, const DataHeader *pDataHeader, LPCVOID lpDataBuffer);
//向数据文件中写数据(任意数据)
bool writeDataFile(INT64 nOffset, LPCVOID lpBuffer, INT64 dwSize);
//从数据文件中读数据
bool readDataFile(INT64 nOffset, LPVOID lpBuffer, INT64 dwSize) const;
//向索引文件中写数据(任意数据)
bool writeIndexFile(INT64 nOffset, LPCVOID lpBuffer, INT64 dwSize);
protected:
//验证各个列表的数据数量
void validateListCount() const;
//计算一个nChunkSize大小的数据块能够分割出的新数据块的大小如果返回0则表示不能分割
//参数dwDataSize为在nChunkSize大小的数据块中存储的数据大小包含DataHeader的大小非块大小
//将大的数据块拆分为小数据块的条件为:
// 1、数据库头中规定的数据块大小不低于128字节
// 2、数据块写入新数据后剩余空间大于数据库头中的数据块单位大小
// 3、新数据长度是数据块长度的一半以内
INT64 getChunkSplitOutSize(INT64 nChunkSize, INT64 dwDataSize) const;
//获取索引记录在空闲大小列表中的索引
INT_PTR getFreeSizeListIndex(ChunkIndex *pIndex) const;
};
#endif