初始化项目
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* 大字段(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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user