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


    我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我 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

    1.png

    特性如下:

    • 支持「十进制表示的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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    提前做好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,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    手写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 = '';
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    手写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);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    把事件绑定到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));
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    体验地址 & 源码

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

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

    写在最后

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

  • 相关阅读:
    基于Springboot+Vue实现前后端分离商城管理系统
    OSPF高级特性 —— 修改参考带宽值 + 修改优先级
    fabric.js点击group 种的子元素
    智慧能源三维可视化平台实时动态呈现运维状态
    基于 .net core 8.0 的 swagger 文档优化分享-根据命名空间分组显示
    Linux查看磁盘命令df-h详解
    查询sqlserver内存分配情况的SQL
    体验静态代码块
    什么是分布式、什么是分布式调度、分布式调度要实现的目标
    软件测试行业能干多久?“35岁焦虑“成了多少IT人的梦魇。
  • 原文地址:https://blog.csdn.net/kd_2015/article/details/126793176