JavaScript中的字节数组和数字之间的相互转换
最编程
2024-01-15 18:45:14
...
javascript通过ArrayBuffer和DataView实现字节数组和数字之间的相互转换
注意!我这里的所有函数用的都是大端字节序(高位在前,低位在后),即数据的高字节,保存在内存的低地址中,而数据的低字节,保存在内存的高地址中
举例:2个字节的无符号整型1的二进制表示
大端模式: 0000 0000 0000 0001
小端模式: 0000 0001 0000 0000
如果字节序不一致,解析的数据就会出错!如果你的数据是小端模式,就需要翻转数组,或者重写这些函数,DataView的setInt32和getInt32之类的函数可以传入一个参数来控制大端还是小端,我采用的是默认的情况下的大端模式
具体代码如下
test(); function test() { var bytes = getFloat64Bytes(-3.33); alert(bytes); alert(toFloat64(bytes)); } //构建一个视图,把字节数组写到缓存中,索引从0开始,大端字节序 function getView(bytes) { var view = new DataView(new ArrayBuffer(bytes.length)); for (var i = 0; i < bytes.length; i++) { view.setUint8(i, bytes[i]); } return view; } //将字节数组转成有符号的8位整型,大端字节序 function toInt8(bytes) { return getView(bytes).getInt8(); } //将字节数组转成无符号的8位整型,大端字节序 function toUint8(bytes) { return getView(bytes).getUint8(); } //将字节数组转成有符号的16位整型,大端字节序 function toInt16(bytes) { return getView(bytes).getInt16(); } //将字节数组转成无符号的16位整型,大端字节序 function toUint16(bytes) { return getView(bytes).getUint16(); } //将字节数组转成有符号的32位整型,大端字节序 function toInt32(bytes) { return getView(bytes).getInt32(); } //将字节数组转成无符号的32位整型,大端字节序 function toUint32(bytes) { return getView(bytes).getUint32(); } //将字节数组转成32位浮点型,大端字节序 function toFloat32(bytes) { return getView(bytes).getFloat32(); } //将字节数组转成64位浮点型,大端字节序 function toFloat64(bytes) { return getView(bytes).getFloat64(); } //将数值写入到视图中,获得其字节数组,大端字节序 function getUint8Array(len, setNum) { var buffer = new ArrayBuffer(len); //指定字节长度 setNum(new DataView(buffer)); //根据不同的类型调用不同的函数来写入数值 return new Uint8Array(buffer); //创建一个字节数组,从缓存中拿取数据 } //得到一个8位有符号整型的字节数组,大端字节序 function getInt8Bytes(num) { return getUint8Array(1, function (view) { view.setInt8(0, num); }) } //得到一个8位无符号整型的字节数组,大端字节序 function getUint8Bytes(num) { return getUint8Array(1, function (view) { view.setUint8(0, num); }) } //得到一个16位有符号整型的字节数组,大端字节序 function getInt16Bytes(num) { return getUint8Array(2, function (view) { view.setInt16(0, num); }) } //得到一个16位无符号整型的字节数组,大端字节序 function getUint16Bytes(num) { return getUint8Array(2, function (view) { view.setUint16(0, num); }) } //得到一个32位有符号整型的字节数组,大端字节序 function getInt32Bytes(num) { return getUint8Array(4, function (view) { view.setInt32(0, num); }) } //得到一个32位无符号整型的字节数组,大端字节序 function getUint32Bytes(num) { return getUint8Array(4, function (view) { view.setUint32(0, num); }) } //得到一个32位浮点型的字节数组,大端字节序 function getFloat32Bytes(num) { return getUint8Array(4, function (view) { view.setFloat32(0, num); }) } //得到一个64位浮点型的字节数组,大端字节序 function getFloat64Bytes(num) { return getUint8Array(8, function (view) { view.setFloat64(0, num); }) } ////下面几个为另一种实现方式的版本,只实现了简单几种,其他的实现起来比较麻烦,所以就中途放弃了 //function toInt32(bytes) { // return ((bytes[0] & 0xFF) << 24) | ((bytes[1] & 0xFF) << 16) | ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); //} //function toUInt16(bytes) { // return ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF); //} //function toInt16(bytes) { // return bytes[0] >> 7 == 0 ? toUInt16(bytes) : toUInt16(bytes) - 65536; //} //function getInt32Bytes(num) { // return [num >> 24 & 0xFF, num >> 16 & 0xFF, num >> 8 & 0xFF, num & 0xFF]; //} //function getUint16Bytes(num) { // return [num >> 8 & 0xFF, num & 0xFF]; //} //function getInt16Bytes(num) { // return num >= 0 ? getUint16Bytes(num) : getUint16Bytes(65536 + num); //}
还有个小问题,我这边数字转字节数组函数的返回值都是Uint8Array,这是一个TypeArray类型,它和Array不是一个东西,定长的,不能push,而且Array.concat无法正常连接Uint8Array数组(会将Uint8Array整体作为一个对象),如果使用的是Array,就需要自己处理下,或者直接在getUint8Array函数中将Uint8Array转成Array
//将数值写入到视图中,获得其字节数组,大端字节序 function getUint8Array(len, setNum) { var buffer = new ArrayBuffer(len); //指定字节长度 setNum(new DataView(buffer)); //根据不同的类型调用不同的函数来写入数值 var uint8Array = new Uint8Array(buffer); //创建一个字节数组,从缓存中拿取数据 var arr = new Array(); //将Uint8Array转成Array数组,不考虑性能问题 for (var i = 0; i < uint8Array.byteLength; i++) { //尴尬,Uint8Array没有length,只有byteLength,之前写的竟然没测就发布了,现在才发现问题 arr.push(uint8Array[i]); } return arr; }
推荐阅读
-
位、字节、WORD、DWORD 的区别和联系 - Unicode 和 ANSI 的区别就像输入法中 "全宽 "和 "半宽 "的区别一样。 由于不同的 ANSI 编码有不同的标准(不同的字符集),对于给定的多字节字符串,我们必须知道它使用的是哪种字符集,才能知道它包含哪些 "字符"。对于 UNICODE 字符串来说,无论环境如何,它所代表的 "字符 "内容始终是相同的。Unicode 有一个统一的标准,定义了世界上大多数字符的编码,因此拉丁文、数字、简体中文、繁体中文和日文都可以存储在一个编码中。统一码是一个统一的标准,定义了世界上大多数字符的编码。 比特(Bit)和字节(Byte)的区别:例如USB2.0 标准接口的传输速率为 480Mbps,有一些人误认为是每秒 480 兆比特,同样网络带宽为 2MB,就容易误认为是每秒 2 兆比特。其实,480Mbps 应该是 480 兆比特/秒或 480 兆字节/秒,它等于 "60 兆字节/秒";同样,2MB,应该是 256 兆字节/秒。 Bit 和 Byte 译为 "比特",都是数据计量单位,比特="位 "或 "比特"。 Byte = 字节,即 1byte = 8bits,两者的换算关系为 1:8。 Mbps = mega bits per second(兆位/秒)是速率单位,因此 2M 带宽应为 2 兆位/秒,即 2MBps。MB = 兆字节(Megabytes,兆字节)是单位量,1MB/S(兆字节/秒)= 8MBPS(兆字节/秒)。 通常所说的硬盘容量是指 40GB、80GB、100GB,其中的 B 是指 Byte 也称为 "字节"。 1 KB=1024 字节 1 MB=1024 KB=1024*1024 字节 1 GB=1024 MB=1024*1024*1024 字节 例如,以前所谓的 56KB MODEM 转换过来的 56KBps 除以 8 就是 7Kbyte,所以真正从网上下载文件存在硬盘上的速度也是每秒 7Kbyte;也就是说,用 B 表示传输速度一般指 Bit;用 B 表示容量一般指 Byte。比特、字节、WORD、DWORD 的本质。
-
一种结构设计模式,允许在对象中动态添加新行为。它通过创建一个封装器来实现这一目的,即把对象放入一个装饰器类中,然后把这个装饰器类放入另一个装饰器类中,以此类推,形成一个封装器链。这样,我们就可以在不改变原始对象的情况下动态添加新行为或修改原始行为。 在 Java 中,实现装饰器设计模式的步骤如下: 定义一个接口或抽象类作为被装饰对象的基类。 公共接口 Component { void operation; } } 在本例中,我们定义了一个名为 Component 的接口,该接口包含一个名为 operation 的抽象方法,该方法定义了被装饰对象的基本行为。 定义一个实现基类方法的具体装饰对象。 公共类 ConcreteComponent 实现 Component { public class ConcreteComponent implements Component { @Override public void operation { System.out.println("ConcreteComponent is doing something...") ; } } 定义一个抽象装饰器类,该类继承于基类,并将装饰对象作为一个属性。 公共抽象类装饰器实现组件 { protected Component 组件 public Decorator(Component component) { this.component = component; } } @Override public void operation { component.operation; } } } 在这个示例中,我们定义了一个名为 Decorator 的抽象类,它继承了 Component 接口,并将被装饰对象作为一个属性。在操作方法中,我们调用了被装饰对象上的同名方法。 定义一个具体的装饰器类,继承自抽象装饰器类并实现增强逻辑。 公共类 ConcreteDecoratorA extends Decorator { public ConcreteDecoratorA(Component 组件) { super(component); } } public void operation { super.operation System.out.println("ConcreteDecoratorA 正在添加新行为......") ; } } 在本例中,我们定义了一个名为 ConcreteDecoratorA 的具体装饰器类,它继承自装饰器抽象类,并实现了操作方法的增强逻辑。在操作方法中,我们首先调用被装饰对象上的同名方法,然后添加新行为。 使用装饰器增强被装饰对象。 公共类 Main { public static void main(String args) { Component 组件 = new ConcreteComponent; component = new ConcreteDecoratorA(component); 组件操作 } } 在这个示例中,我们首先创建了一个被装饰对象 ConcreteComponent,然后通过 ConcreteDecoratorA 类创建了一个装饰器,并将被装饰对象作为参数传递。最后,调用装饰器的操作方法,实现对被装饰对象的增强。 使用场景 在 Java 中,装饰器模式被广泛使用,尤其是在 I/O 中。Java 中的 I/O 库使用装饰器模式实现了不同数据流之间的转换和增强。 让我们打开文件 a.txt,从中读取数据。InputStream 是一个抽象类,FileInputStream 是专门用于读取文件流的子类。BufferedInputStream 是一个支持缓存的数据读取类,可以提高数据读取的效率,具体代码如下: @Test public void testIO throws Exception { InputStream inputStream = new FileInputStream("C:/bbb/a.txt"); // 实现包装 inputStream = new BufferedInputStream(inputStream); byte bytes = new byte[1024]; int len; while((len = inputStream.read(bytes)) != -1){ System.out.println(new String(bytes, 0, len)); } } } } 其中 BufferedInputStream 对读取数据进行了增强。 这样看来,装饰器设计模式和代理模式似乎有点相似,接下来让我们讨论一下它们之间的区别。 第三,与代理模式的区别: 代理模式的目的是控制对对象的访问,它在对象外部提供一个代理对象来控制对原对象的访问。代理对象和原始对象通常实现相同的接口或继承相同的类,以确保两者可以相互替换。 装饰器模式的目的是动态增强对象的功能,而这是通过对象内部的包装器来实现的。在装饰器模式中,装饰器类和被装饰对象通常实现相同的接口或继承自相同的类,以确保两者可以相互替代。装饰器模式也被称为封装器模式。 在代理模式中,代理类附加了与原类无关的功能。
-
为什么UTF-8 和 GBK 会相互转换,为什么会一团糟?-锟斤拷 "是指在字节和字符的转换(编码和解码)过程中使用了不同的编码,找出编码和解码的编码,修改后使用同一种编码。 ===================== 补充 ========================== 在上面的文章中,其实一直回避了一个问题,那就是既然保存中的所有字符都需要转换成二进制,那么 java 是使用什么编码来保存字符的呢?这个问题我们其实可以不必深究,因为它对我们来说是透明的,我们只需假定 java 使用了某种可以表示所有字符的编码。由于这种透明性,我们可以假设 java 直接保存字符本身,就像上面所说的那样。 在 java 虚拟机中使用的是 unicode 字符集。
-
JavaScript 中数据类型的相互转换以及一元、二元 + 运算符和 == 运算符
-
南邮OJ Web任务大揭秘:层层挑战剖析 1. 挑战一:迷宫般的目录探索 题目作者似乎穷举了所有可能的目录组合,最终在404.php中的