fastfile
Version:
fast cached read write of big binary files
229 lines (173 loc) • 6.29 kB
JavaScript
const PAGE_SIZE = 1<<22;
export function createNew(o) {
const initialSize = o.initialSize || 0;
const fd = new BigMemFile();
fd.o = o;
const nPages = initialSize ? Math.floor((initialSize - 1) / PAGE_SIZE)+1 : 0;
fd.o.data = [];
for (let i=0; i<nPages-1; i++) {
fd.o.data.push( new Uint8Array(PAGE_SIZE));
}
if (nPages) fd.o.data.push( new Uint8Array(initialSize - PAGE_SIZE*(nPages-1)));
fd.totalSize = 0;
fd.readOnly = false;
fd.pos = 0;
return fd;
}
export function readExisting(o) {
const fd = new BigMemFile();
fd.o = o;
fd.totalSize = (o.data.length-1)* PAGE_SIZE + o.data[o.data.length-1].byteLength;
fd.readOnly = true;
fd.pos = 0;
return fd;
}
export function readWriteExisting(o) {
const fd = new BigMemFile();
fd.o = o;
fd.totalSize = (o.data.length-1)* PAGE_SIZE + o.data[o.data.length-1].byteLength;
fd.readOnly = false;
fd.pos = 0;
return fd;
}
const tmpBuff32 = new Uint8Array(4);
const tmpBuff32v = new DataView(tmpBuff32.buffer);
const tmpBuff64 = new Uint8Array(8);
const tmpBuff64v = new DataView(tmpBuff64.buffer);
class BigMemFile {
constructor() {
this.pageSize = 1 << 14; // for compatibility
}
_resizeIfNeeded(newLen) {
if (newLen <= this.totalSize) return;
if (this.readOnly) throw new Error("Reading out of file bounds");
const nPages = Math.floor((newLen - 1) / PAGE_SIZE)+1;
for (let i= Math.max(this.o.data.length-1, 0); i<nPages; i++) {
const newSize = i<nPages-1 ? PAGE_SIZE : newLen - (nPages-1)*PAGE_SIZE;
const p = new Uint8Array(newSize);
if (i == this.o.data.length-1) p.set(this.o.data[i]);
this.o.data[i] = p;
}
this.totalSize = newLen;
}
async write(buff, pos) {
const self =this;
if (typeof pos == "undefined") pos = self.pos;
if (this.readOnly) throw new Error("Writing a read only file");
this._resizeIfNeeded(pos + buff.byteLength);
const firstPage = Math.floor(pos / PAGE_SIZE);
let p = firstPage;
let o = pos % PAGE_SIZE;
let r = buff.byteLength;
while (r>0) {
const l = (o+r > PAGE_SIZE) ? (PAGE_SIZE -o) : r;
const srcView = buff.slice(buff.byteLength - r, buff.byteLength - r + l);
const dstView = new Uint8Array(self.o.data[p].buffer, o, l);
dstView.set(srcView);
r = r-l;
p ++;
o = 0;
}
this.pos = pos + buff.byteLength;
}
async readToBuffer(buffDst, offset, len, pos) {
const self = this;
if (typeof pos == "undefined") pos = self.pos;
if (this.readOnly) {
if (pos + len > this.totalSize) throw new Error("Reading out of bounds");
}
this._resizeIfNeeded(pos + len);
const firstPage = Math.floor(pos / PAGE_SIZE);
let p = firstPage;
let o = pos % PAGE_SIZE;
// Remaining bytes to read
let r = len;
while (r>0) {
// bytes to copy from this page
const l = (o+r > PAGE_SIZE) ? (PAGE_SIZE -o) : r;
const srcView = new Uint8Array(self.o.data[p].buffer, o, l);
buffDst.set(srcView, offset+len-r);
r = r-l;
p ++;
o = 0;
}
this.pos = pos + len;
}
async read(len, pos) {
const self = this;
const buff = new Uint8Array(len);
await self.readToBuffer(buff, 0, len, pos);
return buff;
}
close() {
}
async discard() {
}
async writeULE32(v, pos) {
const self = this;
tmpBuff32v.setUint32(0, v, true);
await self.write(tmpBuff32, pos);
}
async writeUBE32(v, pos) {
const self = this;
tmpBuff32v.setUint32(0, v, false);
await self.write(tmpBuff32, pos);
}
async writeULE64(v, pos) {
const self = this;
tmpBuff64v.setUint32(0, v & 0xFFFFFFFF, true);
tmpBuff64v.setUint32(4, Math.floor(v / 0x100000000) , true);
await self.write(tmpBuff64, pos);
}
async readULE32(pos) {
const self = this;
const b = await self.read(4, pos);
const view = new Uint32Array(b.buffer);
return view[0];
}
async readUBE32(pos) {
const self = this;
const b = await self.read(4, pos);
const view = new DataView(b.buffer);
return view.getUint32(0, false);
}
async readULE64(pos) {
const self = this;
const b = await self.read(8, pos);
const view = new Uint32Array(b.buffer);
return view[1] * 0x100000000 + view[0];
}
async readString(pos) {
const self = this;
const fixedSize = 2048;
let currentPosition = typeof pos == "undefined" ? self.pos : pos;
if (currentPosition > this.totalSize) {
if (this.readOnly) {
throw new Error("Reading out of bounds");
}
this._resizeIfNeeded(pos);
}
let endOfStringFound = false;
let str = "";
while (!endOfStringFound) {
let currentPage = Math.floor(currentPosition / PAGE_SIZE);
let offsetOnPage = currentPosition % PAGE_SIZE;
if (self.o.data[currentPage] === undefined) {
throw new Error("ERROR");
}
let readLength = Math.min(fixedSize, self.o.data[currentPage].length - offsetOnPage);
const dataArray = new Uint8Array(self.o.data[currentPage].buffer, offsetOnPage, readLength);
let indexEndOfString = dataArray.findIndex(element => element === 0);
endOfStringFound = indexEndOfString !== -1;
if (endOfStringFound) {
str += new TextDecoder().decode(dataArray.slice(0, indexEndOfString));
self.pos = currentPage * PAGE_SIZE + offsetOnPage + indexEndOfString + 1;
} else {
str += new TextDecoder().decode(dataArray);
self.pos = currentPage * PAGE_SIZE + offsetOnPage + dataArray.length;
}
currentPosition = self.pos;
}
return str;
}
}