众所周知 : 1个字节(1 byte) = 8个比特(8 bit、位)
一个文件(图片/文本/....)可以通过特定接口读取为 一串二进制数据 (ArrayBuffer、stream .... ) , 如:
using (StreamReader stream = new StreamReader(csvPath))
CsvReader csvReader = new CsvReader(stream, true);
读出来后的 二进制文件 究竟需要怎么解析呢?
首先我们先回顾下:
二进制数组由三类对象组成。
简单说,ArrayBuffer对象代表原始的二进制数据,TypedArray视图用来读写简单类型的二进制数据,DataView视图用来读写复杂类型的二进制数据。
中间那个数字代表着存储的基本位数, 如 Int8Array 就是 8bit , 1个字节(1 byte), 如果不确定 可以通过 .BYTES_PER_ELEMENT 属性来获得单位字节数
总结 ===> 只要我们拿到一串二进制数据(最好能知道 Encoding规则),就可以通过上述对应的二进制数组视图来解析
我们一般用 UTF-8来编码解码 , 故附上一些常用的方法:
.js侧定义:
// 可以解决 中文乱码问题
Uint8ArrayToString(array) {
var out, i, len, c;
var char2, char3;
out = "";
len = array.length;
i = 0;
while (i len) {
c = array[i++];
switch (c >> 4) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
break;
case 12: case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode(((c & 0x1F) 6) | (char2 & 0x3F));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(((c & 0x0F) 12) |
((char2 & 0x3F)
((char3 & 0x3F)
break;
default:
console.error("c >> 4 = ", c >> 4)
break
}
}
return out;
}
Uint8ArrayToInt16Array(value: Uint8Array) {
var a = new Int16Array(value.buffer)
return a;
}
Uint8ArrayToInt32Array(value: Uint8Array) {
var a = new Int32Array(value.buffer)
return a
}
Uint8ArrayToFloat64Array(value: Uint8Array) {
var a = new Float64Array(value.buffer)
return a;
}
.cs 侧使用:
switch (header.FieldType) {
case CsvFieldType.FLOAT:
var byteLength = Float64Array.BYTES_PER_ELEMENT
var view = new Uint8Array(ab)
var a = view.slice(this.byteOffset, this.byteOffset + byteLength)
obj = this.Uint8ArrayToFloat64Array(a)[0]
this.ReadByte(byteLength)
break;
case CsvFieldType.INT:
var byteLength = Int32Array.BYTES_PER_ELEMENT
var view = new Uint8Array(ab)
var a = view.slice(this.byteOffset, this.byteOffset + byteLength)
obj = this.Uint8ArrayToInt32Array(a)[0]
this.ReadByte(byteLength)
break;
case CsvFieldType.SHORT:
var byteLength = Int16Array.BYTES_PER_ELEMENT
var view = new Uint8Array(ab)
var a = view.slice(this.byteOffset, this.byteOffset + byteLength)
obj = this.Uint8ArrayToInt16Array(a)[0]
this.ReadByte(byteLength)
break;
case CsvFieldType.DOUBLE:
var byteLength = Float64Array.BYTES_PER_ELEMENT
var view = new Uint8Array(ab)
var a = view.slice(this.byteOffset, this.byteOffset + byteLength)
obj = this.Uint8ArrayToFloat64Array(a)[0]
this.ReadByte(byteLength)
break;
case CsvFieldType.STRING:
case CsvFieldType.JSON:
//字符串//
var byteLength = Uint8Array.BYTES_PER_ELEMENT
var strLength = this.ReadByte(byteLength)
// console.log("str len = ", strLength)
if (strLength > 128) {
var byteLength = Uint8Array.BYTES_PER_ELEMENT
var strLength2 = this.ReadByte(byteLength)
// console.log("str len2 = ", strLength2)
strLength = strLength2 * 128 + (strLength - 128)
}
var byteLength = strLength
obj = this.Uint8ArrayToString(new Uint8Array(ab, this.byteOffset, byteLength))
this.ReadByte(byteLength)
break;
default:
break;
}
这里值得注意的是:
上面读取字符串的时候,由于我们是用 C# 的Write方法编码的
而C# 的Write方法会先把字符串的(UTF8编码字节串)**长度**写入,长度用的是"可变长编码",因此可能有1/2/3/4等字节不等。
用于长度的每个字节只用7个比特,最高位用来表示是否有后续长度字节。这样作可以节省空间,比如128以内只要用一个字节来表示长度
所以我们解码的时候一定要注意 编码的规则!!!
所以我们解码的时候一定要注意 编码的规则!!!
所以我们解码的时候一定要注意 编码的规则!!!
按照编码的规则进行解析就很简单啦~
学习资料:
【C# BinaryReader.ReadString()方法如何确定从数据流中读多少内容】
【C# 读取字符串的字节长度】
【ES6学习18(二进制数组)】