118 lines
3.4 KiB
Plaintext
118 lines
3.4 KiB
Plaintext
/**
|
|
* 大字段(LOB)分块缓冲(1.7.7)。
|
|
*
|
|
* 大文本/二进制按固定大小分块加载与编辑:
|
|
* - 只请求可视范围内的块,避免一次性读取整列;
|
|
* - 记录已加载块,read 拼接时若跨未加载块则抛错(提示先加载)。
|
|
*/
|
|
export class LobBuffer {
|
|
private readonly totalSize: number;
|
|
private readonly chunkSize: number;
|
|
private readonly loaded: Map<number, string> = new Map<number, string>();
|
|
|
|
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();
|
|
}
|
|
}
|