7

[JS入门到进阶] 手写解析uin8数组的工具:解析二进制字节,太快太方便了!

 1 year ago
source link: https://blog.51cto.com/hullqin/5635319
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

[JS入门到进阶] 手写解析uin8数组的工具:解析二进制字节,太快太方便了!

推荐 原创

大家好,我是公众号「线下聚会游戏」作者HullQin,开发了 《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏。

我常常需要处理二进制数据,每次面对一堆二进制数据,需要自己逐个字节转换为二进制、十六进制,去阅读和理解,去排查问题。

举个例子:有一个场景是我的《我做了个《联机桌游合集: UNO+斗地主+五子棋》无需下载,点开即玩!叫上朋友,即刻开局!不看广告,不做任务,享受「纯粹」的游戏!》,我使用websocket传输二进制数据,我使用protocol buffer作为序列化协议。所有变量的值都用二进制表示,变量名也被用数字表示,体积很小。(另一种常见的序列化协议是JSON,它是基于文本的序列化协议,空间利用率低,所以常常需要压缩)

  • 使用二进制的好处:空间利用率高,节约带宽,提升速度。
  • 使用二进制的坏处:调试难度高,开发成本高。

二进制的数据,通常用uin8数组表示。8个二进制位就表示一个byte(十进制的值为0-255,即十六进制的00至FF)。我们调试二进制数据时,也常常看到诸如43 6c 6f 73表示的数据,这是十六进制,每2位十六进制,就代表了一个byte。当然,也有时候使用十进制打印的。

但是十进制、十六进制,都不能满足诉求。

有时候我们需要以二进制表示,直观看清每一位的值。有时候我们需要把整体作为一个整数(例如整体作为uint32),看看它的值。有时候我们需要把每位按照ASCII转换为字符串,也许它是有语义的。

网上有些现成的进制转换工具,但都不好用。而且有广告,比较慢。

所以我自己做了一个工具:  https://tool.hullqin.cn/byte-parser.html

[JS入门到进阶] 手写解析uin8数组的工具:解析二进制字节,太快太方便了!_前端

特性如下:

  • 支持「十进制表示的uint8数组、二进制表示的uint8数组、十六进制表示的uint8数组、整体作为数字、ASCII数组」互相转换。任意输入一项,只要合法,其它项都会自动生成,并且格式化,并且生成的结果你可以随意编辑,再次转换。
  • 如果出错,有错误提示,并保留上一次结果。
  • 如果字节过多(超过6),那么(整体的值)10进制意义不大,就不再计算了。
  • 如果有一个字节不是合法的ASCII,就认为ASCII无意义,就不再计算了。
  • 轻量级、开源,不到200行代码,如有定制化需求,可fork代码后修改。

核心html:

<div>
  <label for="bytes-10">(uin8数组)10进制:</label>
  <br/>
  <textarea id="bytes-10"></textarea>
</div>
<div>
  <label for="bytes-2">(uin8数组)2进制:</label>
  <br/>
  <textarea id="bytes-2"></textarea>
</div>
<div>
  <label for="bytes-16">(uin8数组)16进制:</label>
  <br/>
  <textarea id="bytes-16"></textarea>
</div>
<div>
  <label for="value-int">(整体的值)10进制:</label>
  <br/>
  <textarea id="value-int"></textarea>
</div>
<div>
  <label for="value-ascii">(整体的值)ASCII:</label>
  <br/>
  <textarea id="value-ascii"></textarea>
</div>
<div id="message"></div>

提前做好dom查询,存储下来,避免以后再查:

let bytes = [];
const bytesEle = {
  2: document.getElementById('bytes-2'),
  10: document.getElementById('bytes-10'),
  16: document.getElementById('bytes-16'),
};
const valueIntEle = document.getElementById('value-int');
const valueAsciiEle = document.getElementById('value-ascii');
const messageEle = document.getElementById('message');
const pad = {
  2: 8,
  16: 2,
  10: 0,
};

手写setter,即设置bytes的函数:

const setBytes = (value) => {
  bytes = value;
  console.log(bytes);
  Object.keys(bytesEle).forEach(radix => {
    const ele = bytesEle[radix];
    ele.value = bytes.map(i => i.toString(radix).padStart(pad[radix], '0').padStart(8, ' ')).join(' ')
  });
  if (bytes.length > 6) {
    valueIntEle.value = '';
  } else {
    valueIntEle.value = bytes.map((i, index) => i * (256 ** (bytes.length - 1 - index))).reduce((a, b) => a + b).toString();
  }
  if (bytes.every(i => (i === 10 || i > 31) && i < 127)) {
    valueAsciiEle.value = String.fromCharCode(...bytes);
  } else {
    valueAsciiEle.value = '';
  }
};

手写click触发的事件,根据输入内容,生成bytes,然后调用setBytes,触发视图更新:

const changeBytesByUin8 = (value, radix) => {
  const newBytes = value.split(/[^\da-fA-F]+/).filter(i => i !== '').map(i => parseInt(i.trim(), radix));
  if (newBytes.findIndex(i => Number.isNaN(i) || i >= 256) >= 0) {
    throw new Error('解析失败');
  }
  setBytes(newBytes);
};
const changeBytesByInt = (value) => {
  let num = Number(value.trim());
  if (Number.isNaN(num)) {
    throw new Error('解析失败');
  }
  const result = [];
  while (num > 0) {
    result.splice(0, 0, num % 256);
    num = Math.floor(num / 256);
  }
  setBytes(result);
};
const changeBytesByAscii = (value) => {
  const result = Array.from(value).map(i => i.charCodeAt(0));
  if (result.findIndex(i => i > 256) >= 0) {
    throw new Error('解析失败');
  }
  setBytes(result);
};

把事件绑定到dom上:

const onUint8ValueChange = (radix) => (event) => {
  try {
    messageEle.innerText = '';
    changeBytesByUin8(event.target.value.trim(), radix);
  } catch (e) {
    messageEle.innerText = e.message;
  }
}
Object.keys(bytesEle).forEach(radix => {
  bytesEle[radix].addEventListener('change', onUint8ValueChange(radix));
});

体验地址 & 源码

体验地址:  https://tool.hullqin.cn/byte-parser.html

源码:  https://github.com/HullQin/tool-hullqin-cn

我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了 《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还独立开发了 《合成大西瓜重制版》。还开发了 《Dice Crush》参加Game Jam 2022。喜欢可以关注我  HullQin 噢~我有空了会分享做游戏的相关技术。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK