@cloudpss/ubjson 0.5.34 → 0.5.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{benchmark-string.js → benchmark-string-decode.js} +4 -4
- package/benchmark-string-encode.js +32 -0
- package/benchmark-string-size-caculation.js +9 -11
- package/benchmark.js +1 -0
- package/dist/common/decoder.js +2 -1
- package/dist/common/decoder.js.map +1 -1
- package/dist/common/encoder.d.ts +4 -2
- package/dist/common/encoder.js +106 -45
- package/dist/common/encoder.js.map +1 -1
- package/dist/common/errors.d.ts +4 -0
- package/dist/common/errors.js +14 -0
- package/dist/common/errors.js.map +1 -0
- package/dist/common/string-decoder.d.ts +5 -3
- package/dist/common/string-decoder.js +23 -14
- package/dist/common/string-decoder.js.map +1 -1
- package/dist/common/string-encoder.d.ts +32 -2
- package/dist/common/string-encoder.js +105 -12
- package/dist/common/string-encoder.js.map +1 -1
- package/dist/stream-helper/encoder.d.ts +4 -4
- package/dist/stream-helper/encoder.js +116 -41
- package/dist/stream-helper/encoder.js.map +1 -1
- package/package.json +3 -3
- package/src/common/decoder.ts +4 -3
- package/src/common/encoder.ts +106 -48
- package/src/common/errors.ts +14 -0
- package/src/common/string-decoder.ts +63 -54
- package/src/common/string-encoder.ts +103 -14
- package/src/stream-helper/encoder.ts +118 -39
- package/tests/.utils.js +10 -0
- package/tests/e2e/.data.js +470 -0
- package/tests/e2e/no-buffer-text.js +37 -0
- package/tests/e2e/no-buffer.js +30 -0
- package/tests/e2e/no-encode-into.js +32 -0
- package/tests/e2e/no-textencoder-decoder.js +34 -0
- package/tests/e2e/normal.js +27 -0
- package/tests/e2e/stream.js +20 -0
- package/tests/encode.js +11 -19
- package/tests/huge-string.js +7 -9
- package/tests/rxjs/encode.js +4 -18
- package/tests/stream/encode.js +0 -15
- package/tests/string-encoding.js +3 -2
- package/tests/tsconfig.json +2 -1
- package/tests/e2e.js +0 -415
package/src/common/encoder.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { constants } from './constants.js';
|
|
2
|
-
import {
|
|
2
|
+
import { unsupportedType, unsupportedView } from './errors.js';
|
|
3
|
+
import { stringByteLength, encodeInto } from './string-encoder.js';
|
|
4
|
+
|
|
5
|
+
const LARGE_DATA_LENGTH = 65536;
|
|
3
6
|
|
|
4
7
|
/** 编码至 ubjson */
|
|
5
8
|
export abstract class EncoderBase {
|
|
6
|
-
protected readonly stringByteLength = getStringByteLength();
|
|
7
|
-
protected readonly encodeInto = getEncodeInto();
|
|
8
9
|
/** 当前写指针位置 */
|
|
9
10
|
protected length = 0;
|
|
10
11
|
/** 数据 */
|
|
@@ -79,7 +80,9 @@ export abstract class EncoderBase {
|
|
|
79
80
|
if (value === null) {
|
|
80
81
|
this.ensureCapacity(1);
|
|
81
82
|
this.buffer[this.length++] = constants.NULL;
|
|
82
|
-
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (Array.isArray(value)) {
|
|
83
86
|
this.ensureCapacity(1);
|
|
84
87
|
this.buffer[this.length++] = constants.ARRAY;
|
|
85
88
|
const size = value.length;
|
|
@@ -95,7 +98,9 @@ export abstract class EncoderBase {
|
|
|
95
98
|
}
|
|
96
99
|
this.ensureCapacity(1);
|
|
97
100
|
this.buffer[this.length++] = constants.ARRAY_END;
|
|
98
|
-
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!ArrayBuffer.isView(value)) {
|
|
99
104
|
const { toJSON } = value as Record<string, unknown>;
|
|
100
105
|
if (typeof toJSON == 'function') {
|
|
101
106
|
this.write(toJSON.call(value));
|
|
@@ -107,7 +112,7 @@ export abstract class EncoderBase {
|
|
|
107
112
|
const keys = Object.keys(value).sort();
|
|
108
113
|
const size = keys.length;
|
|
109
114
|
for (let index = 0; index < size; index++) {
|
|
110
|
-
const key = keys[index]
|
|
115
|
+
const key = keys[index]!;
|
|
111
116
|
const element = (value as Record<string, unknown>)[key];
|
|
112
117
|
if (element === undefined || typeof element == 'function') continue;
|
|
113
118
|
this.writeStringData(key);
|
|
@@ -115,6 +120,33 @@ export abstract class EncoderBase {
|
|
|
115
120
|
}
|
|
116
121
|
this.ensureCapacity(1);
|
|
117
122
|
this.buffer[this.length++] = constants.OBJECT_END;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (value.byteLength > LARGE_DATA_LENGTH) {
|
|
126
|
+
// ARRAY(1) + TYPE_MARKER(1) + TYPE(1) + COUNT_MARKER(1) + COUNT(5)
|
|
127
|
+
this.ensureCapacity(9);
|
|
128
|
+
this.buffer[this.length++] = constants.ARRAY;
|
|
129
|
+
this.buffer[this.length++] = constants.TYPE_MARKER;
|
|
130
|
+
if (value instanceof Uint8Array) {
|
|
131
|
+
this.buffer[this.length++] = constants.UINT8;
|
|
132
|
+
} else if (value instanceof Int8Array) {
|
|
133
|
+
this.buffer[this.length++] = constants.INT8;
|
|
134
|
+
} else if (value instanceof Int16Array) {
|
|
135
|
+
this.buffer[this.length++] = constants.INT16;
|
|
136
|
+
} else if (value instanceof Int32Array) {
|
|
137
|
+
this.buffer[this.length++] = constants.INT32;
|
|
138
|
+
} else if (value instanceof Float32Array) {
|
|
139
|
+
this.buffer[this.length++] = constants.FLOAT32;
|
|
140
|
+
} else if (value instanceof Float64Array) {
|
|
141
|
+
this.buffer[this.length++] = constants.FLOAT64;
|
|
142
|
+
} else if (value instanceof BigInt64Array) {
|
|
143
|
+
this.buffer[this.length++] = constants.INT64;
|
|
144
|
+
} else {
|
|
145
|
+
unsupportedView(value);
|
|
146
|
+
}
|
|
147
|
+
this.buffer[this.length++] = constants.COUNT_MARKER;
|
|
148
|
+
this.setLength(value.length);
|
|
149
|
+
this.writeLargeTypedArrayData(value);
|
|
118
150
|
} else {
|
|
119
151
|
// ARRAY(1) + TYPE_MARKER(1) + TYPE(1) + COUNT_MARKER(1) + COUNT(MAX5) + DATA
|
|
120
152
|
this.ensureCapacity(9 + value.byteLength);
|
|
@@ -139,7 +171,7 @@ export abstract class EncoderBase {
|
|
|
139
171
|
this.buffer[this.length++] = constants.COUNT_MARKER;
|
|
140
172
|
this.setLength(arrayLength);
|
|
141
173
|
for (let i = 0; i < arrayLength; i++) {
|
|
142
|
-
this.view.setInt16(this.length, value[i]);
|
|
174
|
+
this.view.setInt16(this.length, value[i]!);
|
|
143
175
|
this.length += elementSize;
|
|
144
176
|
}
|
|
145
177
|
} else if (value instanceof Int32Array) {
|
|
@@ -147,7 +179,7 @@ export abstract class EncoderBase {
|
|
|
147
179
|
this.buffer[this.length++] = constants.COUNT_MARKER;
|
|
148
180
|
this.setLength(arrayLength);
|
|
149
181
|
for (let i = 0; i < arrayLength; i++) {
|
|
150
|
-
this.view.setInt32(this.length, value[i]);
|
|
182
|
+
this.view.setInt32(this.length, value[i]!);
|
|
151
183
|
this.length += elementSize;
|
|
152
184
|
}
|
|
153
185
|
} else if (value instanceof Float32Array) {
|
|
@@ -155,7 +187,7 @@ export abstract class EncoderBase {
|
|
|
155
187
|
this.buffer[this.length++] = constants.COUNT_MARKER;
|
|
156
188
|
this.setLength(arrayLength);
|
|
157
189
|
for (let i = 0; i < arrayLength; i++) {
|
|
158
|
-
this.view.setFloat32(this.length, value[i]);
|
|
190
|
+
this.view.setFloat32(this.length, value[i]!);
|
|
159
191
|
this.length += elementSize;
|
|
160
192
|
}
|
|
161
193
|
} else if (value instanceof Float64Array) {
|
|
@@ -163,7 +195,7 @@ export abstract class EncoderBase {
|
|
|
163
195
|
this.buffer[this.length++] = constants.COUNT_MARKER;
|
|
164
196
|
this.setLength(arrayLength);
|
|
165
197
|
for (let i = 0; i < arrayLength; i++) {
|
|
166
|
-
this.view.setFloat64(this.length, value[i]);
|
|
198
|
+
this.view.setFloat64(this.length, value[i]!);
|
|
167
199
|
this.length += elementSize;
|
|
168
200
|
}
|
|
169
201
|
} else if (value instanceof BigInt64Array) {
|
|
@@ -171,11 +203,11 @@ export abstract class EncoderBase {
|
|
|
171
203
|
this.buffer[this.length++] = constants.COUNT_MARKER;
|
|
172
204
|
this.setLength(arrayLength);
|
|
173
205
|
for (let i = 0; i < arrayLength; i++) {
|
|
174
|
-
this.view.setBigInt64(this.length, value[i]);
|
|
206
|
+
this.view.setBigInt64(this.length, value[i]!);
|
|
175
207
|
this.length += elementSize;
|
|
176
208
|
}
|
|
177
209
|
} else {
|
|
178
|
-
|
|
210
|
+
unsupportedView(value);
|
|
179
211
|
}
|
|
180
212
|
}
|
|
181
213
|
return;
|
|
@@ -200,18 +232,18 @@ export abstract class EncoderBase {
|
|
|
200
232
|
}
|
|
201
233
|
return;
|
|
202
234
|
default:
|
|
203
|
-
|
|
235
|
+
unsupportedType(value);
|
|
204
236
|
}
|
|
205
237
|
}
|
|
206
238
|
|
|
207
239
|
/** writeStringData */
|
|
208
240
|
private writeStringData(value: string): void {
|
|
209
241
|
const strLength = value.length;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
242
|
+
if (strLength > LARGE_DATA_LENGTH) {
|
|
243
|
+
return this.writeLargeStringData(value);
|
|
244
|
+
}
|
|
245
|
+
// 对于短字符串,直接计算最大使用空间
|
|
246
|
+
const maxUsage = strLength * 3;
|
|
215
247
|
// 一次性分配 setLength 和 encodeInto 的空间,避免无法回溯
|
|
216
248
|
// 额外分配 3 字节,避免 encodeInto 无法写入最后一个字符
|
|
217
249
|
this.ensureCapacity(maxUsage + 5 + 3);
|
|
@@ -219,36 +251,7 @@ export abstract class EncoderBase {
|
|
|
219
251
|
// 预估头部大小
|
|
220
252
|
const headerSize = strLength < 128 ? 2 : strLength < 32768 ? 3 : 5;
|
|
221
253
|
const headerPos = this.length;
|
|
222
|
-
|
|
223
|
-
// 优化小字符串
|
|
224
|
-
if (strLength < 0x40 || !this.encodeInto) {
|
|
225
|
-
let c1, c2;
|
|
226
|
-
let strPosition = headerPos + headerSize;
|
|
227
|
-
const target = this.buffer;
|
|
228
|
-
for (let i = 0; i < strLength; i++) {
|
|
229
|
-
c1 = value.charCodeAt(i);
|
|
230
|
-
if (c1 < 0x80) {
|
|
231
|
-
target[strPosition++] = c1;
|
|
232
|
-
} else if (c1 < 0x800) {
|
|
233
|
-
target[strPosition++] = (c1 >> 6) | 0xc0;
|
|
234
|
-
target[strPosition++] = (c1 & 0x3f) | 0x80;
|
|
235
|
-
} else if ((c1 & 0xfc00) === 0xd800 && ((c2 = value.charCodeAt(i + 1)) & 0xfc00) === 0xdc00) {
|
|
236
|
-
c1 = 0x1_0000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff);
|
|
237
|
-
i++;
|
|
238
|
-
target[strPosition++] = (c1 >> 18) | 0xf0;
|
|
239
|
-
target[strPosition++] = ((c1 >> 12) & 0x3f) | 0x80;
|
|
240
|
-
target[strPosition++] = ((c1 >> 6) & 0x3f) | 0x80;
|
|
241
|
-
target[strPosition++] = (c1 & 0x3f) | 0x80;
|
|
242
|
-
} else {
|
|
243
|
-
target[strPosition++] = (c1 >> 12) | 0xe0;
|
|
244
|
-
target[strPosition++] = ((c1 >> 6) & 0x3f) | 0x80;
|
|
245
|
-
target[strPosition++] = (c1 & 0x3f) | 0x80;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
bufLength = strPosition - headerPos - headerSize;
|
|
249
|
-
} else {
|
|
250
|
-
bufLength = this.encodeInto(value, this.buffer, headerPos + headerSize);
|
|
251
|
-
}
|
|
254
|
+
const bufLength = encodeInto(value, this.buffer, this.length + headerSize);
|
|
252
255
|
if (bufLength < 128) {
|
|
253
256
|
this.buffer[this.length++] = constants.INT8;
|
|
254
257
|
this.buffer[this.length++] = bufLength;
|
|
@@ -270,6 +273,61 @@ export abstract class EncoderBase {
|
|
|
270
273
|
this.length += bufLength;
|
|
271
274
|
}
|
|
272
275
|
|
|
276
|
+
/** 写入大字符串 */
|
|
277
|
+
protected writeLargeStringData(value: string): void {
|
|
278
|
+
const binLen = stringByteLength(value);
|
|
279
|
+
this.ensureCapacity(5);
|
|
280
|
+
this.buffer[this.length++] = constants.INT32;
|
|
281
|
+
this.view.setInt32(this.length, binLen);
|
|
282
|
+
this.length += 4;
|
|
283
|
+
this.ensureCapacity(binLen);
|
|
284
|
+
encodeInto(value, this.buffer, this.length);
|
|
285
|
+
this.length += binLen;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** 写入数组 */
|
|
289
|
+
protected writeLargeTypedArrayData(value: ArrayBufferView): void {
|
|
290
|
+
this.ensureCapacity(value.byteLength);
|
|
291
|
+
if (value instanceof Uint8Array || value instanceof Int8Array) {
|
|
292
|
+
// fast path for typed arrays with `BYTES_PER_ELEMENT` of 1
|
|
293
|
+
this.buffer.set(value, this.length);
|
|
294
|
+
this.length += value.byteLength;
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const arrayLength = (value as Int16Array | Int32Array | BigInt64Array | Float32Array | Float64Array).length;
|
|
299
|
+
const elementSize = (value as Int16Array | Int32Array | BigInt64Array | Float32Array | Float64Array)
|
|
300
|
+
.BYTES_PER_ELEMENT;
|
|
301
|
+
if (value instanceof Int16Array) {
|
|
302
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
303
|
+
this.view.setInt16(this.length, value[i]!);
|
|
304
|
+
this.length += elementSize;
|
|
305
|
+
}
|
|
306
|
+
} else if (value instanceof Int32Array) {
|
|
307
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
308
|
+
this.view.setInt32(this.length, value[i]!);
|
|
309
|
+
this.length += elementSize;
|
|
310
|
+
}
|
|
311
|
+
} else if (value instanceof Float32Array) {
|
|
312
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
313
|
+
this.view.setFloat32(this.length, value[i]!);
|
|
314
|
+
this.length += elementSize;
|
|
315
|
+
}
|
|
316
|
+
} else if (value instanceof Float64Array) {
|
|
317
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
318
|
+
this.view.setFloat64(this.length, value[i]!);
|
|
319
|
+
this.length += elementSize;
|
|
320
|
+
}
|
|
321
|
+
} else if (value instanceof BigInt64Array) {
|
|
322
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
323
|
+
this.view.setBigInt64(this.length, value[i]!);
|
|
324
|
+
this.length += elementSize;
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
unsupportedView(value);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
273
331
|
/**
|
|
274
332
|
* 写入整形数字,选取合适的大小,需提前分配空间
|
|
275
333
|
*/
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** unsupported view */
|
|
2
|
+
export function unsupportedView(view: ArrayBufferView): never {
|
|
3
|
+
const type = Object.prototype.toString.call(view).slice(8, -1);
|
|
4
|
+
throw new Error(`Unsupported array buffer view of type ${type}`);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** unsupported type */
|
|
8
|
+
export function unsupportedType(value: unknown): never {
|
|
9
|
+
if (value && typeof value == 'string') {
|
|
10
|
+
throw new Error(`Unsupported type ${value}`);
|
|
11
|
+
}
|
|
12
|
+
const type = Object.prototype.toString.call(value).slice(8, -1);
|
|
13
|
+
throw new Error(`Unsupported type ${type}`);
|
|
14
|
+
}
|
|
@@ -1,40 +1,33 @@
|
|
|
1
|
-
/* c8 ignore next 2: TextDecoder always present, fallback tested */
|
|
2
|
-
export const textDecoder =
|
|
3
|
-
typeof TextDecoder == 'function' ? new TextDecoder('utf8', { ignoreBOM: true, fatal: false }) : null;
|
|
4
|
-
|
|
5
|
-
/* c8 ignore next: TextDecoder always present, fallback tested */
|
|
6
|
-
export const TEXT_ENCODER_THRESHOLD = textDecoder == null ? 0xffff_ffff : 200;
|
|
7
|
-
|
|
8
1
|
const CHUNK_SIZE = 0x1000;
|
|
9
2
|
const REPLACE_CHAR = 0xfffd;
|
|
10
3
|
|
|
11
4
|
const fromCharCode = String.fromCharCode;
|
|
12
5
|
|
|
13
6
|
/** 解码 */
|
|
14
|
-
export function
|
|
7
|
+
export function jsDecode(bytes: Uint8Array, begin: number, end: number): string {
|
|
15
8
|
let offset = begin;
|
|
16
9
|
|
|
17
10
|
const units: number[] = [];
|
|
18
11
|
let result = '';
|
|
19
12
|
while (offset < end) {
|
|
20
|
-
const byte1 = bytes[offset++]
|
|
13
|
+
const byte1 = bytes[offset++]!;
|
|
21
14
|
if ((byte1 & 0x80) === 0) {
|
|
22
15
|
// 1 byte
|
|
23
16
|
units.push(byte1);
|
|
24
17
|
} else if ((byte1 & 0xe0) === 0xc0) {
|
|
25
18
|
// 2 bytes
|
|
26
|
-
const byte2 = bytes[offset++] & 0x3f;
|
|
19
|
+
const byte2 = bytes[offset++]! & 0x3f;
|
|
27
20
|
units.push(((byte1 & 0x1f) << 6) | byte2);
|
|
28
21
|
} else if ((byte1 & 0xf0) === 0xe0) {
|
|
29
22
|
// 3 bytes
|
|
30
|
-
const byte2 = bytes[offset++] & 0x3f;
|
|
31
|
-
const byte3 = bytes[offset++] & 0x3f;
|
|
23
|
+
const byte2 = bytes[offset++]! & 0x3f;
|
|
24
|
+
const byte3 = bytes[offset++]! & 0x3f;
|
|
32
25
|
units.push(((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3);
|
|
33
26
|
} else if ((byte1 & 0xf8) === 0xf0) {
|
|
34
27
|
// 4 bytes
|
|
35
|
-
const byte2 = bytes[offset++] & 0x3f;
|
|
36
|
-
const byte3 = bytes[offset++] & 0x3f;
|
|
37
|
-
const byte4 = bytes[offset++] & 0x3f;
|
|
28
|
+
const byte2 = bytes[offset++]! & 0x3f;
|
|
29
|
+
const byte3 = bytes[offset++]! & 0x3f;
|
|
30
|
+
const byte4 = bytes[offset++]! & 0x3f;
|
|
38
31
|
let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
|
|
39
32
|
if (unit > 0xffff) {
|
|
40
33
|
unit -= 0x1_0000;
|
|
@@ -63,7 +56,7 @@ export function decodeJs(bytes: Uint8Array, begin: number, end: number): string
|
|
|
63
56
|
function longStringInJS(buf: Uint8Array, begin: number, length: number): string | undefined {
|
|
64
57
|
const bytes = Array.from<number>({ length });
|
|
65
58
|
for (let i = 0; i < length; i++) {
|
|
66
|
-
const byte = buf[begin++]
|
|
59
|
+
const byte = buf[begin++]!;
|
|
67
60
|
if ((byte & 0x80) > 0) {
|
|
68
61
|
return;
|
|
69
62
|
}
|
|
@@ -77,95 +70,95 @@ function shortStringInJS(buf: Uint8Array, begin: number, length: number): string
|
|
|
77
70
|
if (length < 2) {
|
|
78
71
|
if (length === 0) return '';
|
|
79
72
|
else {
|
|
80
|
-
const a = buf[begin++]
|
|
73
|
+
const a = buf[begin++]!;
|
|
81
74
|
if ((a & 0x80) > 1) {
|
|
82
75
|
return;
|
|
83
76
|
}
|
|
84
77
|
return fromCharCode(a);
|
|
85
78
|
}
|
|
86
79
|
} else {
|
|
87
|
-
const a = buf[begin++]
|
|
88
|
-
const b = buf[begin++]
|
|
80
|
+
const a = buf[begin++]!;
|
|
81
|
+
const b = buf[begin++]!;
|
|
89
82
|
if ((a & 0x80) > 0 || (b & 0x80) > 0) {
|
|
90
83
|
return;
|
|
91
84
|
}
|
|
92
85
|
if (length < 3) return fromCharCode(a, b);
|
|
93
|
-
const c = buf[begin++]
|
|
86
|
+
const c = buf[begin++]!;
|
|
94
87
|
if ((c & 0x80) > 0) {
|
|
95
88
|
return;
|
|
96
89
|
}
|
|
97
90
|
return fromCharCode(a, b, c);
|
|
98
91
|
}
|
|
99
92
|
} else {
|
|
100
|
-
const a = buf[begin++]
|
|
101
|
-
const b = buf[begin++]
|
|
102
|
-
const c = buf[begin++]
|
|
103
|
-
const d = buf[begin++]
|
|
93
|
+
const a = buf[begin++]!;
|
|
94
|
+
const b = buf[begin++]!;
|
|
95
|
+
const c = buf[begin++]!;
|
|
96
|
+
const d = buf[begin++]!;
|
|
104
97
|
if ((a & 0x80) > 0 || (b & 0x80) > 0 || (c & 0x80) > 0 || (d & 0x80) > 0) {
|
|
105
98
|
return;
|
|
106
99
|
}
|
|
107
100
|
if (length < 6) {
|
|
108
101
|
if (length === 4) return fromCharCode(a, b, c, d);
|
|
109
102
|
else {
|
|
110
|
-
const e = buf[begin++]
|
|
103
|
+
const e = buf[begin++]!;
|
|
111
104
|
if ((e & 0x80) > 0) {
|
|
112
105
|
return;
|
|
113
106
|
}
|
|
114
107
|
return fromCharCode(a, b, c, d, e);
|
|
115
108
|
}
|
|
116
109
|
} else if (length < 8) {
|
|
117
|
-
const e = buf[begin++]
|
|
118
|
-
const f = buf[begin++]
|
|
110
|
+
const e = buf[begin++]!;
|
|
111
|
+
const f = buf[begin++]!;
|
|
119
112
|
if ((e & 0x80) > 0 || (f & 0x80) > 0) {
|
|
120
113
|
return;
|
|
121
114
|
}
|
|
122
115
|
if (length < 7) return fromCharCode(a, b, c, d, e, f);
|
|
123
|
-
const g = buf[begin++]
|
|
116
|
+
const g = buf[begin++]!;
|
|
124
117
|
if ((g & 0x80) > 0) {
|
|
125
118
|
return;
|
|
126
119
|
}
|
|
127
120
|
return fromCharCode(a, b, c, d, e, f, g);
|
|
128
121
|
} else {
|
|
129
|
-
const e = buf[begin++]
|
|
130
|
-
const f = buf[begin++]
|
|
131
|
-
const g = buf[begin++]
|
|
132
|
-
const h = buf[begin++]
|
|
122
|
+
const e = buf[begin++]!;
|
|
123
|
+
const f = buf[begin++]!;
|
|
124
|
+
const g = buf[begin++]!;
|
|
125
|
+
const h = buf[begin++]!;
|
|
133
126
|
if ((e & 0x80) > 0 || (f & 0x80) > 0 || (g & 0x80) > 0 || (h & 0x80) > 0) {
|
|
134
127
|
return;
|
|
135
128
|
}
|
|
136
129
|
if (length < 10) {
|
|
137
130
|
if (length === 8) return fromCharCode(a, b, c, d, e, f, g, h);
|
|
138
131
|
else {
|
|
139
|
-
const i = buf[begin++]
|
|
132
|
+
const i = buf[begin++]!;
|
|
140
133
|
if ((i & 0x80) > 0) {
|
|
141
134
|
return;
|
|
142
135
|
}
|
|
143
136
|
return fromCharCode(a, b, c, d, e, f, g, h, i);
|
|
144
137
|
}
|
|
145
138
|
} else if (length < 12) {
|
|
146
|
-
const i = buf[begin++]
|
|
147
|
-
const j = buf[begin++]
|
|
139
|
+
const i = buf[begin++]!;
|
|
140
|
+
const j = buf[begin++]!;
|
|
148
141
|
if ((i & 0x80) > 0 || (j & 0x80) > 0) {
|
|
149
142
|
return;
|
|
150
143
|
}
|
|
151
144
|
if (length < 11) return fromCharCode(a, b, c, d, e, f, g, h, i, j);
|
|
152
|
-
const k = buf[begin++]
|
|
145
|
+
const k = buf[begin++]!;
|
|
153
146
|
if ((k & 0x80) > 0) {
|
|
154
147
|
return;
|
|
155
148
|
}
|
|
156
149
|
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k);
|
|
157
150
|
} else {
|
|
158
|
-
const i = buf[begin++]
|
|
159
|
-
const j = buf[begin++]
|
|
160
|
-
const k = buf[begin++]
|
|
161
|
-
const l = buf[begin++]
|
|
151
|
+
const i = buf[begin++]!;
|
|
152
|
+
const j = buf[begin++]!;
|
|
153
|
+
const k = buf[begin++]!;
|
|
154
|
+
const l = buf[begin++]!;
|
|
162
155
|
if ((i & 0x80) > 0 || (j & 0x80) > 0 || (k & 0x80) > 0 || (l & 0x80) > 0) {
|
|
163
156
|
return;
|
|
164
157
|
}
|
|
165
158
|
if (length < 14) {
|
|
166
159
|
if (length === 12) return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l);
|
|
167
160
|
else {
|
|
168
|
-
const m = buf[begin++]
|
|
161
|
+
const m = buf[begin++]!;
|
|
169
162
|
if ((m & 0x80) > 0) {
|
|
170
163
|
begin -= 13;
|
|
171
164
|
return;
|
|
@@ -173,13 +166,13 @@ function shortStringInJS(buf: Uint8Array, begin: number, length: number): string
|
|
|
173
166
|
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m);
|
|
174
167
|
}
|
|
175
168
|
} else {
|
|
176
|
-
const m = buf[begin++]
|
|
177
|
-
const n = buf[begin++]
|
|
169
|
+
const m = buf[begin++]!;
|
|
170
|
+
const n = buf[begin++]!;
|
|
178
171
|
if ((m & 0x80) > 0 || (n & 0x80) > 0) {
|
|
179
172
|
return;
|
|
180
173
|
}
|
|
181
174
|
if (length < 15) return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n);
|
|
182
|
-
const o = buf[begin++]
|
|
175
|
+
const o = buf[begin++]!;
|
|
183
176
|
if ((o & 0x80) > 0) {
|
|
184
177
|
return;
|
|
185
178
|
}
|
|
@@ -190,6 +183,14 @@ function shortStringInJS(buf: Uint8Array, begin: number, length: number): string
|
|
|
190
183
|
}
|
|
191
184
|
}
|
|
192
185
|
|
|
186
|
+
let TEXT_DECODER: TextDecoder | null;
|
|
187
|
+
let TEXT_DECODER_THRESHOLD: number;
|
|
188
|
+
|
|
189
|
+
/** 解码 */
|
|
190
|
+
export function nativeDecode(data: Uint8Array, begin: number, end: number): string {
|
|
191
|
+
return TEXT_DECODER!.decode(data.subarray(begin, end));
|
|
192
|
+
}
|
|
193
|
+
|
|
193
194
|
/** 字符串解码,无缓存 */
|
|
194
195
|
export function decode(data: Uint8Array, begin: number, end: number): string {
|
|
195
196
|
const length = end - begin;
|
|
@@ -198,22 +199,22 @@ export function decode(data: Uint8Array, begin: number, end: number): string {
|
|
|
198
199
|
if (result != null) return result;
|
|
199
200
|
}
|
|
200
201
|
// 只有小字符串有优化价值,见 benchmark-string.js
|
|
201
|
-
if (length <
|
|
202
|
+
if (length < TEXT_DECODER_THRESHOLD) {
|
|
202
203
|
// 为小字符串优化
|
|
203
|
-
return
|
|
204
|
+
return jsDecode(data, begin, end);
|
|
204
205
|
}
|
|
205
206
|
// 使用系统解码
|
|
206
|
-
return
|
|
207
|
+
return nativeDecode(data, begin, end);
|
|
207
208
|
}
|
|
208
209
|
|
|
209
|
-
const
|
|
210
|
+
const KEY_CACHE = Array.from<{ value: string; buffer: Uint8Array } | undefined>({ length: 4096 });
|
|
210
211
|
|
|
211
212
|
/** 字符串解码,使用缓存 */
|
|
212
213
|
export function decodeKey(data: Uint8Array, begin: number, end: number): string {
|
|
213
214
|
const length = end - begin;
|
|
214
215
|
const cacheKey =
|
|
215
|
-
((length << 5) ^ (length > 1 ? data[begin] & (data[begin + 1] << 8) : length > 0 ? data[begin] : 0)) & 0xfff;
|
|
216
|
-
let entry =
|
|
216
|
+
((length << 5) ^ (length > 1 ? data[begin]! & (data[begin + 1]! << 8) : length > 0 ? data[begin]! : 0)) & 0xfff;
|
|
217
|
+
let entry = KEY_CACHE[cacheKey];
|
|
217
218
|
if (entry != null && entry.buffer.byteLength === length) {
|
|
218
219
|
let i = 0;
|
|
219
220
|
for (; i < length; i++) {
|
|
@@ -225,15 +226,23 @@ export function decodeKey(data: Uint8Array, begin: number, end: number): string
|
|
|
225
226
|
let str = length < 16 ? shortStringInJS(data, begin, length) : longStringInJS(data, begin, length);
|
|
226
227
|
if (str == null) {
|
|
227
228
|
// 只有小字符串有优化价值,见 benchmark-string.js
|
|
228
|
-
if (length <
|
|
229
|
+
if (length < TEXT_DECODER_THRESHOLD) {
|
|
229
230
|
// 为小字符串优化
|
|
230
|
-
str =
|
|
231
|
+
str = jsDecode(data, begin, end);
|
|
231
232
|
} else {
|
|
232
233
|
// 使用系统解码
|
|
233
|
-
str =
|
|
234
|
+
str = nativeDecode(data, begin, end);
|
|
234
235
|
}
|
|
235
236
|
}
|
|
236
237
|
entry = { value: str, buffer: data.slice(begin, end) };
|
|
237
|
-
|
|
238
|
+
KEY_CACHE[cacheKey] = entry;
|
|
238
239
|
return str;
|
|
239
240
|
}
|
|
241
|
+
|
|
242
|
+
/** 重设环境 */
|
|
243
|
+
export function resetEnv(): void {
|
|
244
|
+
TEXT_DECODER = typeof TextDecoder == 'function' ? new TextDecoder('utf8', { ignoreBOM: true, fatal: false }) : null;
|
|
245
|
+
TEXT_DECODER_THRESHOLD = TEXT_DECODER == null ? 0xffff_ffff : 16;
|
|
246
|
+
KEY_CACHE.fill(undefined);
|
|
247
|
+
}
|
|
248
|
+
resetEnv();
|