@cloudpss/ubjson
Version:
161 lines • 5.61 kB
JavaScript
import { encode, stringByteLength } from '../helper/string-encoder.js';
import { EncoderBase } from '../base/encoder.js';
const BLOCK_SIZE = 1024 * 64; // 64 KiB
const MAX_SIZE = 1024 * 1024 * 32; // 32 MiB
/** 保存一个内存池以减少重复分配 */
let POOL = null;
/** 获取内存池 */
function alloc(size) {
if (POOL == null || size !== BLOCK_SIZE) {
return new Uint8Array(size);
}
const pool = POOL;
POOL = null;
return pool;
}
/** 归还内存池 */
function free(buf) {
if (POOL == null && buf.byteLength === BLOCK_SIZE) {
POOL = buf;
return true;
}
return false;
}
/** 流式编码 UBJSON */
export class StreamEncoderHelper extends EncoderBase {
constructor(options, onChunk) {
super();
this.onChunk = onChunk;
this.sortObjectKeys = options?.sortObjectKeys ?? false;
this.data = alloc(BLOCK_SIZE);
this.view = new DataView(this.data.buffer);
}
/**
* 销毁实例,释放内存池
*/
destroy() {
free(this.data);
const self = this;
self.view = null;
self.data = null;
}
/**
* 确保 buffer 还有 capacity 的空闲空间
*/
ensureCapacity(capacity) {
if (capacity > MAX_SIZE) {
// 超过最大尺寸限制
throw new Error('Buffer has exceed max size');
}
// 无需扩容
if (capacity >= 0 && this.data.byteLength >= this.length + capacity)
return;
const CURRENT_SIZE = this.data.byteLength;
const NEXT_SIZE = Math.max(capacity, BLOCK_SIZE);
const REUSE_BUF = CURRENT_SIZE >= NEXT_SIZE && // 满足容量需求
CURRENT_SIZE - BLOCK_SIZE < NEXT_SIZE; // 不过于浪费
// 提交目前的数据
if (REUSE_BUF) {
this.onChunk(this.data.slice(0, this.length));
}
else {
if (free(this.data)) {
// 归还内存池成功,buffer 可能重用,需要拷贝数据
this.onChunk(this.data.slice(0, this.length));
}
else {
this.onChunk(this.data.subarray(0, this.length));
}
// 重新分配缓冲区
this.data = alloc(NEXT_SIZE);
this.view = new DataView(this.data.buffer);
}
this.length = 0;
}
/** @inheritdoc */
writeLargeStringData(value) {
const strLen = value.length;
const binLen = stringByteLength(value);
this.ensureCapacity(5);
this.data[this.length++] = 108 /* constants.INT32 */;
this.view.setInt32(this.length, binLen);
this.length += 4;
this.ensureCapacity(-1);
// divide string to 64k chunks
for (let i = 0; i < strLen; i += BLOCK_SIZE) {
let end = i + BLOCK_SIZE;
// avoid split surrogate pair
// eslint-disable-next-line unicorn/prefer-code-point
const endAtSurrogate = end < strLen && (value.charCodeAt(end) & 0xfc00) === 0xdc00;
if (endAtSurrogate) {
end--;
}
const chunk = value.slice(i, end);
this.onChunk(encode(chunk));
if (endAtSurrogate) {
i--;
}
}
}
/** @inheritdoc */
writeLargeTypedArrayData(type, value) {
this.ensureCapacity(-1);
const { byteLength } = value;
if (type === 85 /* constants.UINT8 */ || type === 105 /* constants.INT8 */) {
// fast path for typed arrays with `BYTES_PER_ELEMENT` of 1
// divide buffer to 64k chunks
const { buffer, byteOffset } = value;
for (let i = 0; i < byteLength; i += BLOCK_SIZE) {
this.onChunk(new Uint8Array(buffer.slice(byteOffset + i, byteOffset + i + BLOCK_SIZE)));
}
return;
}
if (type === 68 /* constants.FLOAT64 */) {
const arrayLength = byteLength / 8;
for (let i = 0; i < arrayLength; i++) {
this.ensureCapacity(8);
this.view.setFloat64(this.length, value[i]);
this.length += 8;
}
}
else if (type === 108 /* constants.INT32 */) {
const arrayLength = byteLength / 4;
for (let i = 0; i < arrayLength; i++) {
this.ensureCapacity(4);
this.view.setInt32(this.length, value[i]);
this.length += 4;
}
}
else if (type === 76 /* constants.INT64 */) {
const arrayLength = byteLength / 8;
for (let i = 0; i < arrayLength; i++) {
this.ensureCapacity(8);
this.view.setBigInt64(this.length, value[i]);
this.length += 8;
}
}
else if (type === 100 /* constants.FLOAT32 */) {
const arrayLength = byteLength / 4;
for (let i = 0; i < arrayLength; i++) {
this.ensureCapacity(4);
this.view.setFloat32(this.length, value[i]);
this.length += 4;
}
}
else {
(type);
const arrayLength = byteLength / 2;
for (let i = 0; i < arrayLength; i++) {
this.ensureCapacity(2);
this.view.setInt16(this.length, value[i]);
this.length += 2;
}
}
}
/** 获取写入结果 */
encode(value) {
this.writeValue(value);
this.ensureCapacity(-1);
}
}
//# sourceMappingURL=encoder.js.map