blog icon indicating copy to clipboard operation
blog copied to clipboard

了解一下字节码

Open eyasliu opened this issue 4 years ago • 0 comments

字节码编程

字节码,在编程中无处不在,但是在业务层中也许您也用不着,但是了解一下还是有好处的。开发稍微底层一些的逻辑基本都会碰到

常识与基本概念

我们都听过一些概念:

  • int 类型占用32位
  • 一个 ascii 码字符占一个字节
  • 一个 utf8 汉字字符占用3个或4个字节(utf8编码字节长度是可变的,占用1-6个字节不等)
  • 0B00010100 这是一个二进制数字(2^4 + 2^2),024 这是一个8进制的数字(8^1 * 2 + 8^0 * 4), 0X14 这是一个16进制数字(16^1 * 1 + 16^0 * 4),他们都是表示十进制的 20
  • 操作系统有 32 位和 64 位

上面这些例子中,有些单位是字节(byte),有些是 (bit),这个 指的是比特位,也就是二进制位, 8 个比特位等于 1 个字节 8bit = 1byte,为什么大多数情况要把比特换算成字节来表示呢?因为计算机中的内存条的最小单元就是字节。可以理解成内存条是有一个个格子组成的,每个格子存储一个字节,也就是 8 个比特位。每个格子都有它的唯一编号,称为内存地址。

当操作系统是32位时,最多能标记 2^32 个格子,也就是 4GB,当操作系统是64位时,最多能标记 2^64个格子,也就是 17179869184GB。所以说32位的系统的内存最多只能有4GB,推荐现在的所有操作系统都升级到 64 位的。

另外在某些编程语言中,如Go, C, int 类型的长度是和操作系统有关的,在32位系统上int是32位,在64位系统上int是64位的,当然有些编程语言是固定长度的,如java的int永远都是32位,下文为了统一口径,将 int 类型都视为 32 位

深入

编程语言的类型

var num int = 0X14

比如C语言的 int 类型,它是 32 位的,也就是 4 个字节,一个int类型数字需要占用 4 个内存格子,那么放在内存应该是这样存储的(字节之间没有空格,这里只是为了好看才加的空格)

0X00 00 00 14

转成字节(8进制)

0B0000 0000 0000 0024

所以还是转成16进制好看些,我们看到的内存地址也通常是16进制的。

大序端与小序端

将高位数的地址放在前面还是后面,这成了个问题,可能以前做计算机的大佬们没商量好,就导致有了大序端和小序端。有点类似于文字的方向 ltr 和 rtl,也就是文字是从左往右念,还是从右往左念,大部分语言是从左往右的了,比如中文和英文。

  • 大序端,把高位放在前面,比如数字 21,读作: 二十一, 2 是高位,在前面,1是低位放在后面,对应内存的存储也是,高位的地址放在前面,低位放在后面
  • 小序端,和大序端相反,把高位放在后面,自行脑补,反正用得少,也不用去理解小序端

另外也叫大端法、小端法,或者大端对齐、小端对齐,其实说的都是一个概念。在网络传输中有规定,必须要使用大序端进行传输,于是在网络这么发达的今天,大序端是主流。小序端比较少遇到,遇到的时候麻烦您再转一次吧

后文的所有示例,都基于大序端

操作技巧

类型转换

大部分编程语言都会有 byte 或者 uint8 这个类型,它的一个值就是一个字节,它是跟内存中的字节对等的,又因为所有的运行中的值都是存在内存中的,所以字节数组可以用来表示所有数据,那是不是字节数组可以和所有的数据类型进行转换呢,可以,但是这前提是理解编程语言内部的序列和反序列化才能这么干,实际上即使知道理解了也不会那么干。

但是对于常用的数据类型和字节数组的转换我们还是得需要知道如何转换的。

数字转字节数组 []byte

数字的转换其实上文作为例子已经展示过了,总结一下流程

  1. 确定该数字类型的长度,每 8 位一个字节,如 int32 的字节数组长度是 4 ,int64 是的字节数组长度是 8
  2. 把数字换算成16进制,回填到低位字节位,其余字节位用0补齐数组长度

如把 int32 类型的 20 数字转成字节数组

20 = 0X00000014
bytes = [4]byte{0X00,0X00,0X00,0X14}

事实上,这种计算方法是很 low 的,更好的方法应该是使用位移才对。

字符串转字节数组 []byte

字符串是有编码的,常用的编码有 ascii, utf8, gbk 等等,通常推荐只用 utf8, 在学习字符串转字节数组前,我们先来了解下什么是字符串编码吧。

字符编码

简单的解释就是一个映射表(码表),一个id(也称:码点、码位)对应了一个字符,不同编码的不同点在于他们的映射表有多大,占用多少空间,仅此而已。

  • 如 ascii 编码,先看看它的映射表,一个数字对应一个英文字符,码表总共只有 127 个记录,一个字符占用的空间永远是 1 个字节
  • 如 utf8 编码,也看看它的映射表,很大吧,根本看不完,所以utf8 可以表示非常非常多的字符,但是utf8的编码是兼容 ascii 的,也就是ascii编码可以直接转成utf8不会有任何异常,utf8 的一个字符占用空间长度是可变的,1 ~ 6 字节之间,它能表示 2^(6 * 8) = 2^48 个字符,足够容纳进去所有语言的文字了吧,其中utf8的编码有些规范,自行搜索去了解吧
  • 其他如 gbk,gb2312的原理和utf8一样,只是这是专为中文而优化的字符编码,每个字符占用的空间没有utf8那么多,也就是内存占用会少一些,但对于当今来说这点内存占用真的不算什么吧。

提到了 utf8,那就不得不说一下 Unicode 了。

  • Unicode 它是一个字符集,它为每一个字符分配一个码点,它是一个标准,一个规范,定义了一系列的规则。
  • utf8, utf16, utf32 是字符编码,他们实现了编码和解码的过程。他们的码点是跟 Unicode 不一样的,但是都会有一套固定的转换规则,所以根据 Unicode 码点和他们的码点是可以互转的

他们的关系有点像是接口和实现类,但又不是那么回事。


再继续看看字符转字节数组的操作,这个其实没什么好说的,按照码表对照来转就是了,比如 utf8 编码的 根据码表查到就是 0Xe6b7a6,转成字节数组 []byte{e6, b7, a6},它的 Unicode 码点是 \u6DE6

字符串转字节数组同理,就是多个字符串的转换结果拼接起来。通常不需要我们自己去计算,按理说标准库应该都直接有集成,还是直接用标准库吧。

// Golang 例子
str := "你说得对"
bytes := []byte(str)

JavaScript 中的字节码

JS 有没有 byte 类型?没有。但它有 ArrayBuffer,但是 ArrayBuffer 它是用来存储数据的,它的值无法修改,无法给它赋值,需要转成 TypedArray 或者 DataView 才能修改。TypedArray 和 DataView 的用途有些不一样

  • TypedArray 就是数组,就是用来操作数组一样去操作二进制数据,比如遍历(map,forEach,every,entries,reduce等)、过滤(filter)、查找(find,indexOf,includes等)、合并、切片(slice,splice)、填充(fill),等等,自由度很高,任意操作
  • DataView 是为了将数值转换成 ArrayBuffer,注意是只处理数值

TypedArrayDataView 实例有什么方法我就不多说,直接看文档来得实在,这里提供几个例子

数值类型转ArrayBuffer

JavaScript 的类型中,它只有 Number 这个数据类型,它的类型长度其实我不知道,但是我知道它最大能安全处理的数字是 2^53 - 1,好像长度是 53,超过这数字处理不了,但是也还能存储大致的值不会报错,所以,千万别用 64 位去存储js的数字,那是不可靠的,我建议把js的number转成4个字节(即32位)。

function numToBuffer(num) {
    const buffer = new ArrayBuffer(4) // 4个字节长度,即 32 位
    const view = new DataView(buffer) // ArrayBuffer 不能直接编辑,只能转换成 DataView 或者 TypedArray 才能编辑,这里是处理数值,所以用 DataView
    view.setInt32(0, num) // 从第0个字节开始填充 num 以 int32 类型转成的字节
    return view.buffer // 再把 DataView 转成 ArrayBuffer
}

字符串转 ArrayBuffer

还记得当时字符串转字节数组说要根据字符编码吗,js的字符串默认是 Unicode 编码,得需要转成 utf8 编码。怎么转呢?还是用开源库吧,这个: encode-utf8 ,它还有个对应的utf8解码库: decode-utf8

Golang 中的字节码

Golang 的基础类型 byteuint8 他们是等价的

type byte = uint8

所以字节数组就是 []byte

数值类型 和 []byte 互转

使用标准库做类型转换

import (
    "bytes"
    "encoding/binary"
)

func IntToByteArray(num int64) []byte {
    buf := new(bytes.Buffer)
	binary.Write(buf, binary.BigEndian, num)
	return buf.Bytes()
}

func ByteArrayToInt(arr []byte) int64 {
    data := binary.BigEndian.Uint64(arr)
	return int64(data)
}

字符串 与 []byte 互转

这个写过go的应该没有人不知道了,不啰嗦了

总结

字节码操作在编程过程中是非常重要的技能点,用不用得着另外说,学会了至少能装逼,会让人不明觉厉。

eyasliu avatar Sep 08 '20 09:09 eyasliu