编码解码原理——二进制数组视图数据结构解析

众所周知 : 1个字节(1 byte) = 8个比特(8 bit、位)

一个文件(图片/文本/....)可以通过特定接口读取为 一串二进制数据 (ArrayBuffer、stream .... ) , 如:


C# 

     using (StreamReader stream = new StreamReader(csvPath))

     CsvReader csvReader = new CsvReader(stream, true);

  •  File.ReadAllBytes(fullPath);

Laya:

  •  Laya.loader.load("res/" + csvName + ".csv", Laya.Handler.create(this, this.onComplete), null, Laya.Loader.BUFFER);

Cocos Creator:

  • cc.loader.loadRes

Wechat:

  •   FileManager.readFile(filePath, Encoding)

读出来后的 二进制文件 究竟需要怎么解析呢?


首先我们先回顾下:

二进制数组由三类对象组成。

  1. ArrayBuffer对象:代表内存之中的一段二进制数据,可以通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。
  2. TypedArray视图:共包括 9 种类型的视图,比如Uint8Array(无符号 8 位整数)数组视图, Int16Array(16 位整数)数组视图, Float32Array(32 位浮点数)数组视图等等。
  3. DataView视图:可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号 8 位整数)、第二、三个字节是 Int16(16 位整数)、第四个字节开始是 Float32(32 位浮点数)等等,此外还可以自定义字节序。

简单说,ArrayBuffer对象代表原始的二进制数据,TypedArray视图用来读写简单类型的二进制数据,DataView视图用来读写复杂类型的二进制数据。

  • Int8Array:8位有符号整数,长度1个字节。
  • Uint8Array:8位无符号整数,长度1个字节。
  • Uint8ClampedArray:8位无符号整数,长度1个字节,溢出处理不同。
  • Int16Array:16位有符号整数,长度2个字节。
  • Uint16Array:16位无符号整数,长度2个字节。
  • Int32Array:32位有符号整数,长度4个字节。
  • Uint32Array:32位无符号整数,长度4个字节。
  • Float32Array:32位浮点数,长度4个字节。
  • Float64Array:64位浮点数,长度8个字节。

中间那个数字代表着存储的基本位数, 如 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(二进制数组)】

你可能感兴趣的:(编码解码)