/** * 大字段(LOB)分块缓冲(1.7.7)。 * * 大文本/二进制按固定大小分块加载与编辑: * - 只请求可视范围内的块,避免一次性读取整列; * - 记录已加载块,read 拼接时若跨未加载块则抛错(提示先加载)。 */ export class LobBuffer { private readonly totalSize: number; private readonly chunkSize: number; private readonly loaded: Map = new Map(); constructor(totalSize: number, chunkSize: number) { if (chunkSize < 1) { throw new Error('chunkSize must be >= 1'); } this.totalSize = totalSize < 0 ? 0 : totalSize; this.chunkSize = chunkSize; } size(): number { return this.totalSize; } chunkCount(): number { return Math.ceil(this.totalSize / this.chunkSize); } chunkLength(index: number): number { if (index < 0 || index >= this.chunkCount()) { return 0; } const start: number = index * this.chunkSize; const remaining: number = this.totalSize - start; return remaining < this.chunkSize ? remaining : this.chunkSize; } /** 覆盖 [offset, offset+length) 需要的块号。 */ chunksFor(offset: number, length: number): number[] { if (this.totalSize === 0 || length <= 0) { return []; } const start: number = offset < 0 ? 0 : offset; const end: number = Math.min(this.totalSize, start + length); if (end <= start) { return []; } const first: number = Math.floor(start / this.chunkSize); const last: number = Math.floor((end - 1) / this.chunkSize); const out: number[] = []; for (let index = first; index <= last; index++) { out.push(index); } return out; } setChunk(index: number, data: string): void { if (index < 0 || index >= this.chunkCount()) { return; } this.loaded.set(index, data); } isLoaded(index: number): boolean { return this.loaded.has(index); } loadedChunks(): number { return this.loaded.size; } loadedBytes(): number { let bytes: number = 0; this.loaded.forEach((data: string, index: number) => { bytes += data.length; }); return bytes; } /** 是否需要加载(覆盖范围内有未加载块)。 */ needsLoad(offset: number, length: number): boolean { const chunks: number[] = this.chunksFor(offset, length); for (let i = 0; i < chunks.length; i++) { if (!this.loaded.has(chunks[i])) { return true; } } return false; } /** 读取区间;跨未加载块时抛错。 */ read(offset: number, length: number): string { const start: number = offset < 0 ? 0 : offset; const end: number = Math.min(this.totalSize, start + length); if (end <= start) { return ''; } const chunks: number[] = this.chunksFor(start, end - start); for (let i = 0; i < chunks.length; i++) { if (!this.loaded.has(chunks[i])) { throw new Error('chunk ' + chunks[i] + ' not loaded'); } } let out: string = ''; for (let i = 0; i < chunks.length; i++) { out += this.loaded.get(chunks[i]) as string; } const sliceStart: number = start - chunks[0] * this.chunkSize; const sliceEnd: number = sliceStart + (end - start); return out.substring(sliceStart, sliceEnd); } /** 全部块已加载。 */ isComplete(): boolean { return this.chunkCount() === 0 || this.loaded.size === this.chunkCount(); } }