一、前言

习惯了AS3和H5的ByteArray操作机制,查了下api。没发现Flutter有类似的API,所以自己重写用Flutter的语言写了一遍。主要用于websocket通讯过程中的二进制流的数据转换。

二、实现功能

主要实现了一些基础的二进制字节码的转换功能

1.字符串转换功能
2.byte、short、int、float、double的数值
3.布尔值
4.字节本身的读取

三、实现原理:

最底层肯定是利用了byte这个字节的操作。使用的API是Flutter提供的Uint8List这个API。因为是Uint8List每次申请是固定长度,非常不方便我们的进行动态的读写。所以封装了一层ByteArray,支持动态扩展。

官方介绍Uint8List class

A fixed-length list of 8-bit unsigned integers.For long lists, this implementation can be considerably more space- and time-efficient than the default List implementation.Integers stored in the list are truncated to their low eight bits, interpreted as an unsigned 8-bit integer with values in the range 0 to 255.

下面是直接上代码:

import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';class EndianConst {static int littleEndian= 0;static int bigEndian = 1;
}class ByteArraySize {static int _sizeOfBoolean = 1;//SIZE_OF_BOOLEAN = 1;static int _sizeOfInt8 = 1;//SIZE_OF_INT8 = 1;static int _sizeOfInt16 = 2;//SIZE_OF_INT16 = 2;static int _sizeOfInt32 = 4;//SIZE_OF_INT32 = 4;static int _sizeOfInt64 = 8;//SIZE_OF_INT64 = 8;static int _sizeOfUint8 = 1;//SIZE_OF_UINT8 = 1;static int _sizeOfUint16 = 2;//SIZE_OF_UINT16 = 2;static int _sizeOfUint32 = 4;//SIZE_OF_UINT32 = 4;static int _sizeOfUint64 = 8;//SIZE_OF_UINT64 = 8;static int _sizeOfFloat32 = 4;//SIZE_OF_FLOAT32 = 4;static int _sizeOfFloat64 = 8;//SIZE_OF_FLOAT64 = 8;
}/** ByteArray 类提供用于优化读取、写入以及处理二进制数据的方法和属性。* 注意:ByteArray 类适用于需要在字节层访问数据的高级开发人员。*/
class ByteArray {int bufferExtSize = 0; //Buffer expansion sizeByteData data;Uint8List _bytes;int _position = 0;/*** 已经使用的字节偏移量* @protected* @type {number}* @memberOf ByteArray*/int write_position = 0;Endian $endian = Endian.big;/// 默认一个byteArray生成100个字节大小ByteArray(Uint8List byteList,int bufferExtSize) {this.bufferExtSize = bufferExtSize;if (this.bufferExtSize < 100) {this.bufferExtSize = 100;}this.write_position = 0;this._position = 0;this._bytes = byteList;this.data = ByteData.view(byteList.buffer);this.$endian = Endian.big;}/// 创建一个主要是用来写的ByteArrayfactory ByteArray.createWriteBytes([int bufferExtSize = 2000]){if (bufferExtSize < 0) {bufferExtSize = 0;}ByteArray bytes = new ByteArray(new Uint8List(bufferExtSize),bufferExtSize);return bytes;}/// 创建一个只读对应的字节的ByteArrayfactory ByteArray.createReadBytes(Uint8List byteList){ByteArray bytes = new ByteArray(byteList,byteList.lengthInBytes);return bytes;}/** 可读的剩余字节数* @returns* @memberOf ByteArray*/getReadAvailable() {return this.write_position - this._position;}ByteBuffer getBuffer() {//        return this.data.buffer.slice(0, this.write_position);}ByteBuffer getRawBuffer() {return this.data.buffer;}Uint8List getBytes() {return this._bytes;}Uint8List getWriteBytes() {return this._bytes.sublist(0,this._position);}ByteData getDataView() {return this.data;}int getBufferOffset() {return this.data.offsetInBytes;}/** 将文件指针的当前位置(以字节为单位)移动或返回到 ByteArray 对象中。下一次调用读取方法时将在此位置开始读取,或者下一次调用写入方法时将在此位置开始写入。*/int getPosition() {return this._position;}setPosition(int value) {this._position = value;if (value > this.write_position) {this.write_position = value;}}/** ByteArray 对象的长度(以字节为单位)。* 如果将长度设置为大于当前长度的值,则用零填充字节数组的右侧。* 如果将长度设置为小于当前长度的值,将会截断该字节数组。*/int getLength() {return this._position;}setLength(int value) {this.write_position = value;if (this.data.lengthInBytes > value) {this._position = value;}this._validateBuffer(value);}_validateBuffer(int value) {if (this.data.lengthInBytes < value) {Uint8List tmp = new Uint8List(value + 100);tmp.addAll(this._bytes);this._bytes = tmp;this.data = ByteData.view(tmp.buffer);}}/** 可从字节数组的当前位置到数组末尾读取的数据的字节数。* 每次访问 ByteArray 对象时,将 bytesAvailable 属性与读取方法结合使用,以确保读取有效的数据。*/int getBytesAvailable() {return this.data.lengthInBytes - this._position;}/** 清除字节数组的内容,并将 length 和 position 属性重置为 0。*/void clear() {this._bytes = new Uint8List(this.bufferExtSize);this.data = ByteData.view(this._bytes.buffer);this._position = 0;this.write_position = 0;}/** 从字节流中读取布尔值。读取单个字节,如果字节非零,则返回 true,否则返回 false* @return 如果字节不为零,则返回 true,否则返回 false*/bool readBoolean() {//        if (this.validate(ByteArraySize._sizeOfBoolean)) return !! this._bytes[];return this.data.getInt8(this._position++) == 1;}/** 从字节流中读取带符号的字节* @return 介于 -128 和 127 之间的整数*/int readByte() {if (this.validate(ByteArraySize._sizeOfInt8))return this.data.getInt8(this._position++);return 0;}/** 从字节流中读取 length 参数指定的数据字节数。从 offset 指定的位置开始,将字节读入 bytes 参数指定的 ByteArray 对象中,并将字节写入目标 ByteArray 中* @param bytes 要将数据读入的 ByteArray 对象* @param offset bytes 中的偏移(位置),应从该位置写入读取的数据* @param length 要读取的字节数。默认值 0 导致读取所有可用的数据*/void readBytes(ByteArray bytes, [int offset = 0, length = 0]) {if (bytes == null) { //由于bytes不返回,所以new新的无意义return;}int pos = this._position;int available = this.getBytesAvailable() - pos;if (available < 0) {return;}if (length == 0) {length = available;}else if (length > available) {return;}}/** 从字节流中读取一个 IEEE 754 双精度(64 位)浮点数* @return 双精度(64 位)浮点数*/double readDouble() {if (this.validate(ByteArraySize._sizeOfFloat64)) {double value = this.data.getFloat64(this._position, this.$endian);this._position += ByteArraySize._sizeOfFloat64;return value;}return 0;}/** 从字节流中读取一个 IEEE 754 单精度(32 位)浮点数* @return 单精度(32 位)浮点数*/double readFloat() {if (this.validate(ByteArraySize._sizeOfFloat32)) {double value = this.data.getFloat32(this._position, this.$endian);this._position += ByteArraySize._sizeOfFloat32;return value;}return 0;}/** 从字节流中读取一个带符号的 32 位整数* @return 介于 -2147483648 和 2147483647 之间的 32 位带符号整数*/int readInt() {if (this.validate(ByteArraySize._sizeOfInt32)) {int value = this.data.getInt32(this._position, this.$endian);this._position += ByteArraySize._sizeOfInt32;return value;}return 0;}/** 从字节流中读取一个带符号的 16 位整数* @return 介于 -32768 和 32767 之间的 16 位带符号整数*/int readShort() {if (this.validate(ByteArraySize._sizeOfInt16)) {int value = this.data.getInt16(this._position, this.$endian);this._position += ByteArraySize._sizeOfInt16;return value;}return 0;}/** 从字节流中读取无符号的字节* @return 介于 0 和 255 之间的无符号整数*/int readUnsignedByte() {if (this.validate(ByteArraySize._sizeOfUint8))return this._bytes[this._position++];return 0;}/** 从字节流中读取一个无符号的 32 位整数* @return 介于 0 和 4294967295 之间的 32 位无符号整数*/int readUnsignedInt() {if (this.validate(ByteArraySize._sizeOfUint32)) {int value = this.data.getUint32(this._position, this.$endian);this._position += ByteArraySize._sizeOfUint32;return value;}return 0;}/** 从字节流中读取一个无符号的 16 位整数* @return 介于 0 和 65535 之间的 16 位无符号整数*/int readUnsignedShort() {if (this.validate(ByteArraySize._sizeOfUint16)) {int value = this.data.getUint16(this._position, this.$endian);this._position += ByteArraySize._sizeOfUint16;return value;}return 0;}/** 从字节流中读取一个 UTF-8 字符串。假定字符串的前缀是无符号的短整型(以字节表示长度)* @return UTF-8 编码的字符串*/String readUTF() {int length = this.readUnsignedShort();if (length > 0) {return this.readUTFBytes(length);} else {return "";}}/** 从字节流中读取一个由 length 参数指定的 UTF-8 字节序列,并返回一个字符串* @param length 指明 UTF-8 字节长度的无符号短整型数* @return 由指定长度的 UTF-8 字节组成的字符串*/String readUTFBytes(int length) {if (!this.validate(length)) {return "";}ByteData data = this.data;Uint8List bytes = Uint8List.view(data.buffer, data.offsetInBytes + this._position, length);this._position += length;return this._decodeUTF8(bytes);}/// 写入布尔值。根据 value 参数写入单个字节。如果为 true,则写入 1,如果为 false,则写入 0/// @param value 确定写入哪个字节的布尔值。如果该参数为 true,则该方法写入 1;如果该参数为 false,则该方法写入 0void writeBoolean(bool value) {this.validateBuffer(ByteArraySize._sizeOfBoolean);if (value) {this._bytes[this._position++] = 1;}else {this._bytes[this._position++] = 0;}}/// 在字节流中写入一个字节/// 使用参数的低 8 位。忽略高 24 位/// @param value 一个 32 位整数。低 8 位将被写入字节流void writeByte(int value) {this.validateBuffer(ByteArraySize._sizeOfInt8);this._bytes[this._position++] = value & 0xff;}/** 将指定字节数组 bytes(起始偏移量为 offset,从零开始的索引)中包含 length 个字节的字节序列写入字节流* 如果省略 length 参数,则使用默认长度 0;该方法将从 offset 开始写入整个缓冲区。如果还省略了 offset 参数,则写入整个缓冲区* 如果 offset 或 length 超出范围,它们将被锁定到 bytes 数组的开头和结尾* @param bytes ByteArray 对象* @param offset 从 0 开始的索引,表示在数组中开始写入的位置* @param length 一个无符号整数,表示在缓冲区中的写入范围*/void writeBytes(ByteArray bytes, {int offset = 0, int length = 0}) {int writeLength;if (offset < 0) {return;}if (length < 0) {return;} else if (length == 0) {writeLength = bytes.getLength() - offset;} else {writeLength = min(bytes.getLength() - offset, length);}if (writeLength > 0) {this.validateBuffer(writeLength);this._bytes.setRange(this._position, this._position + bytes.getLength(),bytes.getBytes());this._position = this._position + writeLength;}}/** 在字节流中写入一个 IEEE 754 双精度(64 位)浮点数* @param value 双精度(64 位)浮点数*/void writeDouble(double value) {this.validateBuffer(ByteArraySize._sizeOfFloat64);this.data.setFloat64(this._position, value, this.$endian);this._position += ByteArraySize._sizeOfFloat64;}/** 在字节流中写入一个 IEEE 754 单精度(32 位)浮点数* @param value 单精度(32 位)浮点数*/void writeFloat(double value) {this.validateBuffer(ByteArraySize._sizeOfFloat32);this.data.setFloat32(this._position, value, this.$endian);this._position += ByteArraySize._sizeOfFloat32;}/** 在字节流中写入一个带符号的 32 位整数* @param value 要写入字节流的整数*/void writeInt(int value) {this.validateBuffer(ByteArraySize._sizeOfInt32);this.data.setInt32(this._position, value, this.$endian);this._position += ByteArraySize._sizeOfInt32;}void writeLong(int value) {this.validateBuffer(ByteArraySize._sizeOfInt64);this.data.setInt64(this._position, value, this.$endian);this._position += ByteArraySize._sizeOfInt64;}/** 在字节流中写入一个 16 位整数。使用参数的低 16 位。忽略高 16 位* @param value 32 位整数,该整数的低 16 位将被写入字节流*/void writeShort(int value) {this.validateBuffer(ByteArraySize._sizeOfInt16);this.data.setInt16(this._position, value, this.$endian);this._position += ByteArraySize._sizeOfInt16;}/** 在字节流中写入一个无符号的 32 位整数* @param value 要写入字节流的无符号整数*/void writeUnsignedInt(int value) {this.validateBuffer(ByteArraySize._sizeOfUint32);this.data.setUint32(this._position, value, this.$endian);this._position += ByteArraySize._sizeOfUint32;}/** 在字节流中写入一个无符号的 16 位整数* @param value 要写入字节流的无符号整数*/void writeUnsignedShort(int value) {this.validateBuffer(ByteArraySize._sizeOfUint16);this.data.setUint16(this._position, value, this.$endian);this._position += ByteArraySize._sizeOfUint16;}/** 将 UTF-8 字符串写入字节流。先写入以字节表示的 UTF-8 字符串长度(作为 16 位整数),然后写入表示字符串字符的字节* @param value 要写入的字符串值*/void writeUTF(String value) {List<int> utf8bytes = utf8.encode(value);int length = utf8bytes.length;this.validateBuffer(ByteArraySize._sizeOfUint16 + length);this.data.setUint16(this._position, length, this.$endian);this._position += ByteArraySize._sizeOfUint16;this._writeUint8Array(utf8bytes, false);}/** 将 UTF-8 字符串写入字节流。类似于 writeUTF() 方法,但 writeUTFBytes() 不使用 16 位长度的词为字符串添加前缀* @param value 要写入的字符串值*/void writeUTFBytes(String value) {this._writeUint8Array(this._encodeUTF8(value));}String toString() {return "[ByteArray] length:${this.getLength()} , bytesAvailable:${this.getBytesAvailable()}";}/** @private* 将 Uint8Array 写入字节流* @param bytes 要写入的Uint8Array* @param validateBuffer*/void _writeUint8Array(Uint8List bytes, [bool validateBuffer = true]) {int pos = this._position;int npos = pos + bytes.length;if (validateBuffer) {this.validateBuffer(npos);}this._bytes.setRange(pos, npos, bytes);this._position = npos;}/** @param len* @returns* @private*/bool validate(int len) {int bl = this._bytes.length;if (bl > 0 && this._position + len <= bl) {return true;} else {return false;}}/*  PRIVATE METHODS   * @private* @param len* @param needReplace*/void validateBuffer(int len) {this.write_position = len > this.write_position ? len : this.write_position;len += this._position;this._validateBuffer(len);}/** @private* UTF-8 Encoding/Decoding*/Uint8List _encodeUTF8(String str) {int pos = 0;List<int> codePoints = this.stringToCodePoints(str);List<int> outputBytes = [];while (codePoints.length > pos) {int code_point = codePoints[pos++];if (this._inRange(code_point, 0xD800, 0xDFFF)) {this.encoderError(code_point);}else if (this._inRange(code_point, 0x0000, 0x007f)) {outputBytes.add(code_point);} else {int count, offset;if (this._inRange(code_point, 0x0080, 0x07FF)) {count = 1;offset = 0xC0;} else if (this._inRange(code_point, 0x0800, 0xFFFF)) {count = 2;offset = 0xE0;} else if (this._inRange(code_point, 0x10000, 0x10FFFF)) {count = 3;offset = 0xF0;}outputBytes.add(this._div(code_point, pow(64, count)) + offset);while (count > 0) {int temp = this._div(code_point, pow(64, count - 1));outputBytes.add(0x80 + (temp % 64));count -= 1;}}}return Uint8List.fromList(outputBytes);}/** @param data* @returns*/String _decodeUTF8(Uint8List data) {return utf8.decode(data);}/** @private** @param code_point*/encoderError(int code_point) {}/** @private* @param a* @param min* @param max*/_inRange(int a, int min, int max) {return min <= a && a <= max;}/** @private* @param n* @param d*/int _div(int n, int d) {double result = n / d;return result.floor();}/** @private* @param string*/List<int> stringToCodePoints(String string) {/** @type {Array.<number>} */List<int> cps = [];// Based on http://www.w3.org/TR/WebIDL/#idl-DOMStringvar i = 0,n = string.length;while (i < string.length) {var c = string.codeUnitAt(i);if (!this._inRange(c, 0xD800, 0xDFFF)) {cps.add(c);} else if (this._inRange(c, 0xDC00, 0xDFFF)) {cps.add(0xFFFD);} else { // (inRange(c, 0xD800, 0xDBFF))if (i == n - 1) {cps.add(0xFFFD);} else {var d = string.codeUnitAt(i + 1);if (this._inRange(d, 0xDC00, 0xDFFF)) {var a = c & 0x3FF;var b = d & 0x3FF;i += 1;cps.add(0x10000 + (a << 10) + b);} else {cps.add(0xFFFD);}}}i += 1;}return cps;}
}

测试代码是这样的:

void main() async {ByteArray byteArray = ByteArray.createWriteBytes();//写入测试数据byteArray.writeUTF("测试中文和英文efegegeg");byteArray.writeInt(10002322431);byteArray.writeByte(12);byteArray.writeShort(2000);byteArray.writeBoolean(true);byteArray.writeDouble(520.255);//下标设置为0byteArray.setPosition(0);//读取出来,注意读的顺序和写的顺序要一样print(byteArray.readUTF());print(byteArray.readInt());print(byteArray.readByte());print(byteArray.readShort());print(byteArray.readBoolean());print(byteArray.readDouble());
}

输出结果是这样的:

I/flutter (18653): 测试中文和英文efegegeg
I/flutter (18653): 1412387839
I/flutter (18653): 12
I/flutter (18653): 2000
I/flutter (18653): true
I/flutter (18653): 520.255

这个ByteArray目前虽然正式上线在用了,但是还有一些Bug,比如暂时无法动态扩容。后续应该会研究一下Flutter本身有没有类似的实现。

Flutter实现二进制操作对象ByteArray相关推荐

  1. SharePoint 2013 对二进制大型对象(BLOB)进行爬网

    本文是参考MSDN文档做的示例,SharePoint 2013搜索二进制对象(BLOB),通过外部内容类型的方式将外部数据与SharePoint相关联,修改BCD模型,使SharePoint能够爬网外 ...

  2. 5.2 IO流(File类,Propertis配置文件,其他类(打印流,序列流,操作对象的流(序列化接口),随机访问文件的流,管道流,操作基本数据的流,操作数组的流,操作字符串的流),编码表)

    1.File类 IO流的流对象只能操作设备上的数据.File类:1.用来将文件或者文件夹(也称目录)封装成对象. 2.方便对文件和文件夹的属性信息进行操作.(操作文件夹,文件的属性(创建时间,修改时间 ...

  3. 一起谈.NET技术,VS2010测试功能之旅:编码的UI测试(3)-操作对象的识别原理...

    回顾  在之前的两章分别介绍了一个简单的示例, 操作动作的录制原理,通过修改UIMap.UItest文件控制操作动作代码的生成.想必大家对编码的UI测试操作动作的录制应该有一定了解了,在UI操作中,操 ...

  4. JavaScript学习笔记——underscore操作对象的方法

    var obj = {a:'aaa',b:'bbb',c:'ccc'}; 1._.keys(obj)获取对象的所有属性名称 2._.values(obj)获取对象的所有属性值 3._.extend(d ...

  5. DCMTK:创建,写入和读取二进制细分对象

    DCMTK:创建,写入和读取二进制细分对象 创建,写入和读取二进制细分对象 创建,写入和读取二进制细分对象 #include "dcmtk/config/osconfig.h" # ...

  6. java 反射创建对象并赋值_java使用反射创建并操作对象的方法

    Class 对象可以获得该类里的方法(由 Method 对象表示).构造器(由 Constructor 对象表示).成员变量(由 Field 对象表示),这三个类都位于 java.lang.refle ...

  7. JS-面向对象-操作对象的属性 / 检测对象的某个属性是否存在 / 遍历(枚举)对象的属性 / 属性的分类

    操作对象的属性 <!DOCTYPE html> <html lang="en"> <head><meta charset="UT ...

  8. Java通过引用操作对象的“共享”特性

    先来理解一下引用和对象的关系:对于任何引用类型直接创建的变量都是一个引用,这个引用指向这个类型的对象,数据是存在对象中的,对数据的操作实质是通过引用找到对应的对象,在对对象中的数据进行操作. 而题目中 ...

  9. C语言之文件读写探究(四):fwrite、fread(一次读写一块数据(二进制操作))

    相关博文:C语言之文件读写探究(一):fopen.fclose(文件的打开和关闭) 相关博文:C语言之文件读写探究(二):fputc.fgetc.feof(一次读写一个字符(文本操作)) 相关博文:C ...

最新文章

  1. LVI-SAM:紧耦合的激光视觉惯导SLAM系统(Tixiao Shan新作,已开源)
  2. [译]在启用浏览器功能的INFOPATH表单中实现基于SQL SERVER的多级联动的下拉式列表...
  3. 单例模式可以分为懒汉式和饿汉式:     懒汉式单例模式:在类加载时不初始化。     饿汉式单例模式:在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。
  4. Node版本管理nvm的用法
  5. 华为澄清:公司副总裁未发表中美技术还差两万五千里表述
  6. php地址选择插件,微信小程序中关于三级联动地址选择器的实例分享
  7. 如何bat清楚谷歌浏览器缓存_如何解决谷歌浏览器启动页面被篡改?
  8. 就这样进入了前端开发
  9. 使用axis2 services.xml 发布web service
  10. win32com在wps上另存为SaveAs报错
  11. [USACO2008 Mar]土地购买
  12. IPtables中SNAT、DNAT和MASQUERADE的含义
  13. 03-ES6语法:模板字面量(Template Literals)
  14. Beyond Part Models: Person Retrieval with Refined Part Pooling (ECCV2018)
  15. iOS杂谈15—APP被苹果APPStore拒绝的各种原因
  16. 礼多人不怪:跟美国教授通邮件的18种礼仪
  17. 位于0/nut文件里的'Calculated'边界条件是什么意思?【翻译】
  18. java调起喇叭,【音响知识】调音不求人 教你五步轻松调出好声音
  19. 极品,git简介,安装,方法
  20. LC 104. Maximum Depth of Binary Tree

热门文章

  1. android用听筒不断播放音乐
  2. 海豚php显示图片,PS制作有海豚从水中跃出的效果
  3. Windows系统优化(个人整理)
  4. CPU电压的概念 VID Offset Load-Line Calibration
  5. android手机滑不动了怎么办,如何让Android手机更顺滑?
  6. 我的世界服务器 显示合成指令,我的世界的各种指令大家知道多少给我多少吧,我正在做服务器,求命令方块或对话框的指令...
  7. Spring Cloud 微服务及五大组件介绍
  8. 微信小程序中使用vant weapp 的dialog组件
  9. 简单html摩天轮动画效果,HTML5 摩天轮动画
  10. 解谜游戏-STEAM中最杂乱的游戏标签