@cloudpss/ubjson 0.3.6 → 0.3.7
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 +18 -20
- package/benchmark.js +1 -0
- package/dist/{constants.d.ts → common/constants.d.ts} +0 -0
- package/dist/{constants.js → common/constants.js} +0 -0
- package/dist/common/constants.js.map +1 -0
- package/dist/common/decoder.d.ts +48 -0
- package/dist/{decoder.js → common/decoder.js} +13 -18
- package/dist/common/decoder.js.map +1 -0
- package/dist/common/encoder.d.ts +74 -0
- package/dist/common/encoder.js +282 -0
- package/dist/common/encoder.js.map +1 -0
- package/dist/common/string-decoder.d.ts +13 -0
- package/dist/common/string-decoder.js +106 -0
- package/dist/common/string-decoder.js.map +1 -0
- package/dist/{string-encoder.d.ts → common/string-encoder.d.ts} +0 -0
- package/dist/{string-encoder.js → common/string-encoder.js} +0 -0
- package/dist/common/string-encoder.js.map +1 -0
- package/dist/encoder.d.ts +10 -2
- package/dist/encoder.js +2 -283
- package/dist/encoder.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/stream/encoder.d.ts +14 -0
- package/dist/stream/encoder.js +90 -0
- package/dist/stream/encoder.js.map +1 -0
- package/dist/stream/index.d.ts +4 -0
- package/dist/stream/index.js +7 -0
- package/dist/stream/index.js.map +1 -0
- package/package.json +14 -6
- package/src/{constants.ts → common/constants.ts} +0 -0
- package/src/{decoder.ts → common/decoder.ts} +13 -18
- package/src/common/encoder.ts +305 -0
- package/src/common/string-decoder.ts +107 -0
- package/src/{string-encoder.ts → common/string-encoder.ts} +0 -0
- package/src/encoder.ts +4 -305
- package/src/index.ts +14 -2
- package/src/stream/encoder.ts +87 -0
- package/src/stream/index.ts +8 -0
- package/test/encode-stream.js +450 -0
- package/test/string-encoding.js +13 -3
- package/test.js +8 -0
- package/dist/constants.js.map +0 -1
- package/dist/decoder.d.ts +0 -2
- package/dist/decoder.js.map +0 -1
- package/dist/string-decoder.d.ts +0 -9
- package/dist/string-decoder.js +0 -67
- package/dist/string-decoder.js.map +0 -1
- package/dist/string-encoder.js.map +0 -1
- package/src/string-decoder.ts +0 -69
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudpss/ubjson",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"author": "CloudPSS",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -14,8 +14,14 @@
|
|
|
14
14
|
"module": "dist/index.js",
|
|
15
15
|
"types": "dist/index.d.ts",
|
|
16
16
|
"exports": {
|
|
17
|
-
"
|
|
18
|
-
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./stream": {
|
|
22
|
+
"types": "./dist/stream/index.d.ts",
|
|
23
|
+
"default": "./dist/stream/index.js"
|
|
24
|
+
}
|
|
19
25
|
},
|
|
20
26
|
"scripts": {
|
|
21
27
|
"start": "yarn clean && tsc --watch",
|
|
@@ -23,11 +29,13 @@
|
|
|
23
29
|
"prepublishOnly": "yarn build",
|
|
24
30
|
"clean": "rimraf dist",
|
|
25
31
|
"benchmark": "node ./benchmark",
|
|
26
|
-
"test": "c8 --all --src ./src tape-es \"./test/**/*.js\""
|
|
32
|
+
"test": "c8 --all --src ./src tape-es \"./test/**/*.js\"",
|
|
33
|
+
"test:file": "c8 --all --src ./src tape-es"
|
|
27
34
|
},
|
|
28
35
|
"devDependencies": {
|
|
29
|
-
"
|
|
30
|
-
"
|
|
36
|
+
"@msgpack/msgpack": "^2.7.2",
|
|
37
|
+
"c8": "^7.11.3",
|
|
38
|
+
"tape": "^5.5.3",
|
|
31
39
|
"tape-es": "^1.2.17"
|
|
32
40
|
}
|
|
33
41
|
}
|
|
File without changes
|
|
@@ -2,7 +2,7 @@ import * as constants from './constants.js';
|
|
|
2
2
|
import { StringDecoder } from './string-decoder.js';
|
|
3
3
|
|
|
4
4
|
/** decoder */
|
|
5
|
-
class Decoder {
|
|
5
|
+
export class Decoder {
|
|
6
6
|
private readonly view: DataView;
|
|
7
7
|
/** 当前读指针位置 */
|
|
8
8
|
private offset = 0;
|
|
@@ -93,11 +93,12 @@ class Decoder {
|
|
|
93
93
|
case constants.INT64:
|
|
94
94
|
length = this.readInt64Data();
|
|
95
95
|
break;
|
|
96
|
+
default:
|
|
97
|
+
throw new Error(`Unexpected marker '${String.fromCharCode(marker)}'(${marker}) for int length`);
|
|
96
98
|
}
|
|
97
|
-
if (length
|
|
98
|
-
throw new Error(
|
|
99
|
+
if (length < 0) {
|
|
100
|
+
throw new Error('Invalid length');
|
|
99
101
|
}
|
|
100
|
-
if (length < 0) throw new Error('Invalid length');
|
|
101
102
|
return length;
|
|
102
103
|
}
|
|
103
104
|
|
|
@@ -107,9 +108,9 @@ class Decoder {
|
|
|
107
108
|
private readStringData(): string {
|
|
108
109
|
const length = this.readIntLength();
|
|
109
110
|
this.ensureData(length);
|
|
110
|
-
const
|
|
111
|
+
const begin = this.offset;
|
|
111
112
|
this.offset += length;
|
|
112
|
-
return this.stringDecoder.decode(
|
|
113
|
+
return this.stringDecoder.decode(this.data, begin, this.offset);
|
|
113
114
|
}
|
|
114
115
|
/** readHighPrecisionNumberData */
|
|
115
116
|
private readHighPrecisionNumberData(): never {
|
|
@@ -247,7 +248,8 @@ class Decoder {
|
|
|
247
248
|
this.ensureData(count * 8);
|
|
248
249
|
return BigInt64Array.from({ length: count }, () => this.readInt64Data());
|
|
249
250
|
}
|
|
250
|
-
const array =
|
|
251
|
+
const array: unknown[] = [];
|
|
252
|
+
array.length = count;
|
|
251
253
|
for (let i = 0; i < count; i++) {
|
|
252
254
|
array[i] = type == null ? this.read() : this.readData(type);
|
|
253
255
|
}
|
|
@@ -262,24 +264,17 @@ class Decoder {
|
|
|
262
264
|
const object: Array<[string, unknown]> = [];
|
|
263
265
|
while (this.readMarker() !== constants.OBJECT_END) {
|
|
264
266
|
this.offset--;
|
|
265
|
-
|
|
266
|
-
object.push([key, this.read()]);
|
|
267
|
+
object.push([this.readStringData(), this.read()]);
|
|
267
268
|
}
|
|
268
269
|
return Object.fromEntries(object);
|
|
269
270
|
}
|
|
270
271
|
|
|
271
272
|
const { count, type } = markers;
|
|
272
|
-
const object
|
|
273
|
+
const object: Array<[string, unknown]> = [];
|
|
274
|
+
object.length = count;
|
|
273
275
|
for (let i = 0; i < count; i++) {
|
|
274
|
-
|
|
275
|
-
object[i] = [key, type == null ? this.read() : this.readData(type)];
|
|
276
|
+
object[i] = [this.readStringData(), type == null ? this.read() : this.readData(type)];
|
|
276
277
|
}
|
|
277
278
|
return Object.fromEntries(object);
|
|
278
279
|
}
|
|
279
280
|
}
|
|
280
|
-
|
|
281
|
-
/** 解码 */
|
|
282
|
-
export function decode(value: Uint8Array): unknown {
|
|
283
|
-
const decoder = new Decoder(value);
|
|
284
|
-
return decoder.decode();
|
|
285
|
-
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
2
|
+
import * as constants from './constants.js';
|
|
3
|
+
import { StringEncoder } from './string-encoder.js';
|
|
4
|
+
|
|
5
|
+
/** 编码至 ubjson */
|
|
6
|
+
export abstract class EncoderBase {
|
|
7
|
+
/** 字符串编码 */
|
|
8
|
+
protected stringEncoder = new StringEncoder();
|
|
9
|
+
/** 当前写指针位置 */
|
|
10
|
+
protected length = 0;
|
|
11
|
+
/** 数据 */
|
|
12
|
+
protected buffer!: Uint8Array;
|
|
13
|
+
/** buffer 的 DataView */
|
|
14
|
+
protected view!: DataView;
|
|
15
|
+
|
|
16
|
+
constructor(readonly value: unknown) {}
|
|
17
|
+
/**
|
|
18
|
+
* 确保 buffer 还有 capacity 的空闲空间
|
|
19
|
+
*/
|
|
20
|
+
protected abstract ensureCapacity(capacity: number): void;
|
|
21
|
+
/** 写入一个对象 */
|
|
22
|
+
protected write(value: unknown): void {
|
|
23
|
+
switch (typeof value) {
|
|
24
|
+
case 'undefined':
|
|
25
|
+
return this.writeNoOp();
|
|
26
|
+
case 'boolean':
|
|
27
|
+
return this.writeBoolean(value);
|
|
28
|
+
case 'number':
|
|
29
|
+
return this.writeNumber(value);
|
|
30
|
+
case 'string':
|
|
31
|
+
if (value.length === 1 && value.charCodeAt(0) < 255) {
|
|
32
|
+
// 1 byte string
|
|
33
|
+
return this.writeChar(value);
|
|
34
|
+
}
|
|
35
|
+
return this.writeString(value);
|
|
36
|
+
case 'object': {
|
|
37
|
+
if (value === null) {
|
|
38
|
+
return this.writeNull();
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
return this.writeArray(value);
|
|
42
|
+
}
|
|
43
|
+
if (ArrayBuffer.isView(value)) {
|
|
44
|
+
return this.writeTypedArray(value);
|
|
45
|
+
}
|
|
46
|
+
return this.writeObject(value as Record<string, unknown>);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`Unsupported type ${Object.prototype.toString.call(value)}`);
|
|
50
|
+
}
|
|
51
|
+
/** 写入 marker */
|
|
52
|
+
protected writeMarker(marker: number): void {
|
|
53
|
+
this.ensureCapacity(1);
|
|
54
|
+
this.view.setUint8(this.length, marker);
|
|
55
|
+
this.length += 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 写入 marker */
|
|
59
|
+
protected writeNull(): void {
|
|
60
|
+
this.writeMarker(constants.NULL);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** writeNoOp */
|
|
64
|
+
protected writeNoOp(): void {
|
|
65
|
+
this.writeMarker(constants.NO_OP);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** writeBoolean */
|
|
69
|
+
protected writeBoolean(value: boolean): void {
|
|
70
|
+
this.writeMarker(value ? constants.TRUE : constants.FALSE);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** writeInt8 */
|
|
74
|
+
protected writeInt8(value: number): void {
|
|
75
|
+
this.writeMarker(constants.INT8);
|
|
76
|
+
this.writeInt8Data(value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** writeInt8Data */
|
|
80
|
+
protected writeInt8Data(value: number): void {
|
|
81
|
+
this.ensureCapacity(1);
|
|
82
|
+
this.view.setInt8(this.length, value);
|
|
83
|
+
this.length += 1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** writeUint8 */
|
|
87
|
+
protected writeUint8(value: number): void {
|
|
88
|
+
this.writeMarker(constants.UINT8);
|
|
89
|
+
this.writeUint8Data(value);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** writeUint8Data */
|
|
93
|
+
protected writeUint8Data(value: number): void {
|
|
94
|
+
this.ensureCapacity(1);
|
|
95
|
+
this.view.setUint8(this.length, value);
|
|
96
|
+
this.length += 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** writeInt16 */
|
|
100
|
+
protected writeInt16(value: number): void {
|
|
101
|
+
this.writeMarker(constants.INT16);
|
|
102
|
+
this.writeInt16Data(value);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** writeInt16Data */
|
|
106
|
+
protected writeInt16Data(value: number): void {
|
|
107
|
+
this.ensureCapacity(2);
|
|
108
|
+
this.view.setInt16(this.length, value);
|
|
109
|
+
this.length += 2;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** writeInt32 */
|
|
113
|
+
protected writeInt32(value: number): void {
|
|
114
|
+
this.writeMarker(constants.INT32);
|
|
115
|
+
this.writeInt32Data(value);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** writeInt32Data */
|
|
119
|
+
protected writeInt32Data(value: number): void {
|
|
120
|
+
this.ensureCapacity(4);
|
|
121
|
+
this.view.setInt32(this.length, value);
|
|
122
|
+
this.length += 4;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** writeFloat32 */
|
|
126
|
+
protected writeFloat32(value: number): void {
|
|
127
|
+
this.writeMarker(constants.FLOAT32);
|
|
128
|
+
this.writeFloat32Data(value);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** writeFloat32Data */
|
|
132
|
+
protected writeFloat32Data(value: number): void {
|
|
133
|
+
this.ensureCapacity(4);
|
|
134
|
+
this.view.setFloat32(this.length, value);
|
|
135
|
+
this.length += 4;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** writeFloat64 */
|
|
139
|
+
protected writeFloat64(value: number): void {
|
|
140
|
+
this.writeMarker(constants.FLOAT64);
|
|
141
|
+
this.writeFloat64Data(value);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** writeFloat64Data */
|
|
145
|
+
protected writeFloat64Data(value: number): void {
|
|
146
|
+
this.ensureCapacity(8);
|
|
147
|
+
this.view.setFloat64(this.length, value);
|
|
148
|
+
this.length += 8;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** writeChar */
|
|
152
|
+
protected writeChar(value: string): void {
|
|
153
|
+
this.writeMarker(constants.CHAR);
|
|
154
|
+
this.writeCharData(value);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** writeCharData */
|
|
158
|
+
protected writeCharData(value: string): void {
|
|
159
|
+
this.ensureCapacity(1);
|
|
160
|
+
this.view.setUint8(this.length, value.charCodeAt(0));
|
|
161
|
+
this.length += 1;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** writeString */
|
|
165
|
+
protected writeString(value: string): void {
|
|
166
|
+
this.writeMarker(constants.STRING);
|
|
167
|
+
this.writeStringData(value);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** writeStringData */
|
|
171
|
+
protected writeStringData(value: string): void {
|
|
172
|
+
// 优化小字符串
|
|
173
|
+
if (value.length < 32) {
|
|
174
|
+
const buf = this.stringEncoder.encode(value);
|
|
175
|
+
this.writeInt(buf.length);
|
|
176
|
+
this.ensureCapacity(buf.length);
|
|
177
|
+
this.buffer.set(buf, this.length);
|
|
178
|
+
this.length += buf.length;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (this.stringEncoder.encodeInto != null) {
|
|
182
|
+
const maxUsage = value.length * 3;
|
|
183
|
+
const currentPos = this.length;
|
|
184
|
+
// 一次性分配 writeInt 和 encodeInto 的空间,避免无法回溯
|
|
185
|
+
this.ensureCapacity(maxUsage + 4);
|
|
186
|
+
// 先写入最大大小
|
|
187
|
+
const lengthWriter = this.writeInt(maxUsage);
|
|
188
|
+
// 写入文本数据
|
|
189
|
+
const { written } = this.stringEncoder.encodeInto(value, this.buffer.subarray(this.length));
|
|
190
|
+
// 回溯,写入实际大小
|
|
191
|
+
this.length = currentPos;
|
|
192
|
+
lengthWriter.call(this, written as number);
|
|
193
|
+
// 移动指针到写入末尾
|
|
194
|
+
this.length += written as number;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const data = this.stringEncoder.encode(value);
|
|
198
|
+
this.writeInt(data.length);
|
|
199
|
+
this.ensureCapacity(data.length);
|
|
200
|
+
this.buffer.set(data, this.length);
|
|
201
|
+
this.length += data.length;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 写入整形数字,选取合适的大小
|
|
206
|
+
*
|
|
207
|
+
* @throws 无法在 int32 范围内表示
|
|
208
|
+
*/
|
|
209
|
+
protected writeInt(value: number): (this: this, value: number) => void {
|
|
210
|
+
value = value | 0;
|
|
211
|
+
if (value >= -128 && value <= 127) {
|
|
212
|
+
this.writeInt8(value);
|
|
213
|
+
return this.writeInt8;
|
|
214
|
+
}
|
|
215
|
+
if (value >= -32768 && value <= 32767) {
|
|
216
|
+
this.writeInt16(value);
|
|
217
|
+
return this.writeInt16;
|
|
218
|
+
}
|
|
219
|
+
if (value >= -2147483648 && value <= 2147483647) {
|
|
220
|
+
this.writeInt32(value);
|
|
221
|
+
return this.writeInt32;
|
|
222
|
+
}
|
|
223
|
+
/* c8 ignore next 2 */
|
|
224
|
+
throw new Error(`Unsupported int value ${value}: out of range`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** 写入数字,选取合适的大小 */
|
|
228
|
+
protected writeNumber(value: number): void {
|
|
229
|
+
if (Number.isInteger(value) && value >= -2147483648 && value <= 2147483647) {
|
|
230
|
+
if (value >= 0 && value <= 255) return this.writeUint8(value);
|
|
231
|
+
this.writeInt(value);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (!(Number.isNaN(value) || Math.fround(value) === value)) {
|
|
235
|
+
return this.writeFloat64(value);
|
|
236
|
+
}
|
|
237
|
+
// 如果不会损失精度,则使用 32 位浮点
|
|
238
|
+
return this.writeFloat32(value);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** writeObject */
|
|
242
|
+
protected writeObject(value: Record<string, unknown>): void {
|
|
243
|
+
this.writeMarker(constants.OBJECT);
|
|
244
|
+
// 生成稳定的结果以便 hash 计算
|
|
245
|
+
for (const key of Object.keys(value).sort()) {
|
|
246
|
+
const element = value[key];
|
|
247
|
+
if (element === undefined) continue;
|
|
248
|
+
this.writeStringData(key);
|
|
249
|
+
this.write(element);
|
|
250
|
+
}
|
|
251
|
+
this.writeMarker(constants.OBJECT_END);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** writeArray */
|
|
255
|
+
protected writeArray(value: unknown[]): void {
|
|
256
|
+
this.writeMarker(constants.ARRAY);
|
|
257
|
+
for (const v of value) {
|
|
258
|
+
// 在数组中 undefined 也被视作 null 进行序列化
|
|
259
|
+
if (v == null) this.writeNull();
|
|
260
|
+
else this.write(v);
|
|
261
|
+
}
|
|
262
|
+
this.writeMarker(constants.ARRAY_END);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** writeArray */
|
|
266
|
+
protected writeTypedArray(value: ArrayBufferView): void {
|
|
267
|
+
this.writeMarker(constants.ARRAY);
|
|
268
|
+
this.writeMarker(constants.TYPE_MARKER);
|
|
269
|
+
if (value instanceof Uint8Array || value instanceof Int8Array) {
|
|
270
|
+
// fast path for typed arrays with `BYTES_PER_ELEMENT` of 1
|
|
271
|
+
this.writeMarker(value instanceof Uint8Array ? constants.UINT8 : constants.INT8);
|
|
272
|
+
this.writeMarker(constants.COUNT_MARKER);
|
|
273
|
+
this.writeInt(value.length);
|
|
274
|
+
this.ensureCapacity(value.byteLength);
|
|
275
|
+
this.buffer.set(value, this.length);
|
|
276
|
+
this.length += value.byteLength;
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
/** 用于写入的 setter */
|
|
280
|
+
let setValue;
|
|
281
|
+
if (value instanceof Int16Array) {
|
|
282
|
+
this.writeMarker(constants.INT16);
|
|
283
|
+
setValue = this.view.setInt16;
|
|
284
|
+
} else if (value instanceof Int32Array) {
|
|
285
|
+
this.writeMarker(constants.INT32);
|
|
286
|
+
setValue = this.view.setInt32;
|
|
287
|
+
} else if (value instanceof Float32Array) {
|
|
288
|
+
this.writeMarker(constants.FLOAT32);
|
|
289
|
+
setValue = this.view.setFloat32;
|
|
290
|
+
} else if (value instanceof Float64Array) {
|
|
291
|
+
this.writeMarker(constants.FLOAT64);
|
|
292
|
+
setValue = this.view.setFloat64;
|
|
293
|
+
} else {
|
|
294
|
+
throw new Error(`Unsupported typed array type ${Object.prototype.toString.call(value)}`);
|
|
295
|
+
}
|
|
296
|
+
this.writeMarker(constants.COUNT_MARKER);
|
|
297
|
+
this.writeInt(value.length);
|
|
298
|
+
this.ensureCapacity(value.byteLength);
|
|
299
|
+
// 不要在前面 bind,this.ensureCapacity 有可能导致 this.view 指向新的对象
|
|
300
|
+
for (const v of value) {
|
|
301
|
+
setValue.call(this.view, this.length, v);
|
|
302
|
+
this.length += value.BYTES_PER_ELEMENT;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export const textDecoder =
|
|
2
|
+
typeof TextDecoder == 'function' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: false }) : null;
|
|
3
|
+
|
|
4
|
+
export const TEXT_ENCODER_THRESHOLD = textDecoder == null ? 0xffff_ffff : 200;
|
|
5
|
+
|
|
6
|
+
const CHUNK_SIZE = 0x1_000;
|
|
7
|
+
/** 解码 */
|
|
8
|
+
export function decodeJs(bytes: Uint8Array, begin: number, end: number): string {
|
|
9
|
+
let offset = begin;
|
|
10
|
+
|
|
11
|
+
const units: number[] = [];
|
|
12
|
+
let result = '';
|
|
13
|
+
while (offset < end) {
|
|
14
|
+
const byte1 = bytes[offset++];
|
|
15
|
+
if ((byte1 & 0x80) === 0) {
|
|
16
|
+
// 1 byte
|
|
17
|
+
units.push(byte1);
|
|
18
|
+
} else if ((byte1 & 0xe0) === 0xc0) {
|
|
19
|
+
// 2 bytes
|
|
20
|
+
const byte2 = bytes[offset++] & 0x3f;
|
|
21
|
+
units.push(((byte1 & 0x1f) << 6) | byte2);
|
|
22
|
+
} else if ((byte1 & 0xf0) === 0xe0) {
|
|
23
|
+
// 3 bytes
|
|
24
|
+
const byte2 = bytes[offset++] & 0x3f;
|
|
25
|
+
const byte3 = bytes[offset++] & 0x3f;
|
|
26
|
+
units.push(((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3);
|
|
27
|
+
} else if ((byte1 & 0xf8) === 0xf0) {
|
|
28
|
+
// 4 bytes
|
|
29
|
+
const byte2 = bytes[offset++] & 0x3f;
|
|
30
|
+
const byte3 = bytes[offset++] & 0x3f;
|
|
31
|
+
const byte4 = bytes[offset++] & 0x3f;
|
|
32
|
+
let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
|
|
33
|
+
if (unit > 0xffff) {
|
|
34
|
+
unit -= 0x10000;
|
|
35
|
+
units.push(((unit >>> 10) & 0x3ff) | 0xd800);
|
|
36
|
+
unit = 0xdc00 | (unit & 0x3ff);
|
|
37
|
+
}
|
|
38
|
+
units.push(unit);
|
|
39
|
+
} else {
|
|
40
|
+
units.push(byte1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (units.length >= CHUNK_SIZE) {
|
|
44
|
+
result += String.fromCharCode(...units);
|
|
45
|
+
units.length = 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (units.length > 0) {
|
|
50
|
+
result += String.fromCharCode(...units);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 解码 */
|
|
57
|
+
export function decode(data: Uint8Array, begin: number, end: number): string {
|
|
58
|
+
if (
|
|
59
|
+
end - begin <
|
|
60
|
+
TEXT_ENCODER_THRESHOLD // 只有小字符串有优化价值,见 benchmark-string.js
|
|
61
|
+
) {
|
|
62
|
+
// 为小字符串优化
|
|
63
|
+
return decodeJs(data, begin, end);
|
|
64
|
+
}
|
|
65
|
+
// 使用系统解码
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
67
|
+
return textDecoder!.decode(data.subarray(begin, end));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 特别优化字符串解码速度 */
|
|
71
|
+
export class StringDecoder {
|
|
72
|
+
/** 小字符串缓存 */
|
|
73
|
+
private readonly cache = [
|
|
74
|
+
undefined, // 0 字节字符串,直接返回
|
|
75
|
+
undefined, // 1 字节字符串,使用 String.fromCharCode,兼容 UTF8
|
|
76
|
+
new Map<number, string>(), // 2 字节
|
|
77
|
+
new Map<number, string>(), // 3 字节
|
|
78
|
+
new Map<number, string>(), // 4 字节
|
|
79
|
+
new Map<number, string>(), // 5 字节
|
|
80
|
+
new Map<number, string>(), // 6 字节
|
|
81
|
+
] as const;
|
|
82
|
+
|
|
83
|
+
/** 字符串解码 */
|
|
84
|
+
decode(data: Uint8Array, begin: number, end: number): string {
|
|
85
|
+
const length = (end - begin) as 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
86
|
+
// 这里,length 类型为 Uint32
|
|
87
|
+
if (length > 6) return decode(data, begin, end);
|
|
88
|
+
|
|
89
|
+
// 这里,length 类型为 0 | 1 | 2 | 3 | 4 | 5 | 6
|
|
90
|
+
// 为小字符串优化
|
|
91
|
+
if (length === 0) return '';
|
|
92
|
+
if (length === 1) return String.fromCharCode(data[begin]);
|
|
93
|
+
// number 最多存储 6 字节数据
|
|
94
|
+
const cache = this.cache[length];
|
|
95
|
+
// 计算缓存 key
|
|
96
|
+
let key = 0;
|
|
97
|
+
for (let i = begin; i < end; i++) {
|
|
98
|
+
// 不要用位运算,JS 位运算只支持 32 位
|
|
99
|
+
key = key * 0xff + data[i];
|
|
100
|
+
}
|
|
101
|
+
const match = cache.get(key);
|
|
102
|
+
if (match != null) return match;
|
|
103
|
+
const string = decodeJs(data, begin, end);
|
|
104
|
+
cache.set(key, string);
|
|
105
|
+
return string;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
File without changes
|