Initial commit: FluidExplorer:从零实现的 WinUI 3 文件资源管理器替代品(Mica、命令栏、面包屑)
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
namespace FluidExplorer.Services.Search.Usn;
|
||||
|
||||
/// <summary>
|
||||
/// 紧凑文件名池 —— 索引“快且省内存”的关键之一。
|
||||
///
|
||||
/// 所有文件名以 UTF-16 连续存放在若干 <b>定长块</b>(每块 1<<20 个字符 = 2MB)里,
|
||||
/// 整个索引里不会为文件名产生任何 string 对象;查询时直接在这块内存上取 ReadOnlySpan<char>。
|
||||
///
|
||||
/// 偏移编码:<c>(chunkIndex << 20) | offsetInChunk</c>,单个 int 即可寻址 2G 字符。
|
||||
/// 因为块大小固定为 1<<20,而 NTFS 单个文件名最长 255 字符,
|
||||
/// 所以一个名字永远不会跨越两个块 —— 取值时无需拼接。
|
||||
///
|
||||
/// 线程安全:块数组一次性预分配(只写指针不扩容),块本身写入后用 <see cref="Volatile.Write{T}(ref T, T)"/> 发布。
|
||||
/// 查询线程只会读到“已经完整写入”的块;配合索引条数的发布顺序(先写数据、后写 Count),读侧无需加锁。
|
||||
/// </summary>
|
||||
internal sealed class NamePool
|
||||
{
|
||||
internal const int ChunkShift = 20;
|
||||
internal const int ChunkSize = 1 << ChunkShift;
|
||||
internal const int ChunkMask = ChunkSize - 1;
|
||||
/// <summary>NameRef 只用 8 位存长度,NTFS 文件名最长 255 字符,正好用满。</summary>
|
||||
internal const int MaxNameLength = 255;
|
||||
private const int MaxChunks = 2048; // 2048 * 1M = 2G 字符上限
|
||||
|
||||
private readonly char[][] _chunks = new char[MaxChunks][];
|
||||
private int _chunkCount;
|
||||
private int _fill; // 当前块已使用字符数
|
||||
private int _publishedChars;
|
||||
|
||||
internal NamePool()
|
||||
{
|
||||
AddChunk();
|
||||
}
|
||||
|
||||
/// <summary>池中已发布的字符总数(约等于所有名字长度之和)。</summary>
|
||||
internal int TotalChars => _publishedChars;
|
||||
|
||||
/// <summary>写入一个名字,返回它的池内偏移。空名字返回 0(配合 NameRef 的长度字段仍然无歧义)。</summary>
|
||||
internal int Add(ReadOnlySpan<char> name)
|
||||
{
|
||||
if (name.Length == 0) return 0;
|
||||
if (name.Length > ChunkSize) name = name[..ChunkSize];
|
||||
|
||||
if (_fill + name.Length > ChunkSize) AddChunk();
|
||||
|
||||
int chunkIndex = _chunkCount - 1;
|
||||
int offset = (chunkIndex << ChunkShift) | _fill;
|
||||
name.CopyTo(_chunks[chunkIndex].AsSpan(_fill));
|
||||
_fill += name.Length;
|
||||
_publishedChars = (chunkIndex << ChunkShift) + _fill;
|
||||
return offset;
|
||||
}
|
||||
|
||||
/// <summary>取回名字。参数非法时返回空 span(绝不抛异常,读侧可能并发读到正在更新的旧值)。</summary>
|
||||
internal ReadOnlySpan<char> Get(int offset, int length)
|
||||
{
|
||||
if (length <= 0 || offset < 0) return default;
|
||||
int chunkIndex = offset >> ChunkShift;
|
||||
int inChunk = offset & ChunkMask;
|
||||
if (chunkIndex >= _chunkCount) return default;
|
||||
var chunk = Volatile.Read(ref _chunks[chunkIndex]);
|
||||
if (chunk is null || inChunk + length > ChunkSize) return default;
|
||||
return chunk.AsSpan(inChunk, length);
|
||||
}
|
||||
|
||||
private void AddChunk()
|
||||
{
|
||||
if (_chunkCount >= MaxChunks) throw new InvalidOperationException("文件名池已达到容量上限(2G 字符)。");
|
||||
var chunk = new char[ChunkSize];
|
||||
Volatile.Write(ref _chunks[_chunkCount], chunk);
|
||||
_chunkCount++;
|
||||
_fill = 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- NameRef 打包
|
||||
|
||||
/// <summary>把 (偏移, 长度) 打包进一个 long:低 8 位是长度,高 56 位是偏移。</summary>
|
||||
internal static long Pack(int offset, int length) => ((long)offset << 8) | (uint)(length & 0xFF);
|
||||
|
||||
internal static int UnpackOffset(long nameRef) => (int)(nameRef >> 8);
|
||||
|
||||
internal static int UnpackLength(long nameRef) => (int)(nameRef & 0xFF);
|
||||
}
|
||||
Reference in New Issue
Block a user