Skip to content
This repository has been archived by the owner on Nov 21, 2024. It is now read-only.

Latest commit

 

History

History

02-BitOperator

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

位运算符

位运算符是一切二进制数据操作的基础,在js里总共只有7个符号,看似容易但要熟练起来需要一定的练习。

W3school 这篇文章讲得很清晰: ECMAScript 位运算符

简单总结下,所有数据都可以用字节表示,一个字节就是1Byte1024Byte就是1KB这个大家都懂的,但它需要转换成 0101...这种机器能读得多的符号为二进制代码,其中1Byte可以转换成8个这个二进制代码,例如数字1可以转化为 00000001,数字3可以转化为00000010这个也很好理解。

那么,1Byte最大能表示多大的数字呢?那就是二进制代码的11111111啦,转换成10进制就是255

parseInt("11111111", 2) === 255;

但有时候觉得用11111111去表示一个数字,对机器来说是没问题的,但对人的阅读来说可能不是很方便,所以就有了除10进制以外的进制表示方法,例如常用的16进制,那么11111111可以转换成16进制的FF;

(255).toString(16) === "ff";

通常你说的ff鬼知道它是16进制还是32进制啊,所有我们约定在数字前面加上进制表示的符号,例如:

// 16进制前面带有0x, 2进制前面带有0b
0xff === 0b11111111;

题外话,常见的颜色值可以用6位的16进制表示,例如白色为#ffffff,黑色为#000000,请问这个区间可以表示多少种颜色值?

parseInt("ffffff", 16) === 16777215;

大致理解后,再阅读 W3school 的文章就容易很多了,那么就来练习一个简单的场景:

获得有一个字节的流为00011010,其中第4位到第7位(1101)表示了一个无符号整数,我怎么知道这个数是多少?

((0b00011010 >> 1) & 0b00001111) === 13;
// 或者
((0b00011010 >> 1) & 15) === 13;

又或者,现在有两个字节0001101011000000,第一个字节的低位 4 个比特(1010)和第二个字节的高位 2 个比特(11)表示了一个无符号整数,我怎么知道这个数是多少?

(((0b00011010 & 0b00001111) << 2) | (0b11000000 >> 6)) === 43;
// 或者
(((0b00011010 & 15) << 2) | (0b11000000 >> 6)) === 43;

可以看出位运算是内部以二进制进行运算,但都是以10进制输出的,而且给人的感觉就是&用于获取数据,而|则用于写入数据。

字节序

字节序是初学者不容易理解的概念,甚至在操作时会产生疑惑,但实际上并不用太关心,因为一般一种数据就只有一种字节序。

可以看看阮一峰的理解字节序

简单总结下,就是单次写入多个字节的时候,这些字节是怎么排列的,有时候是从大到小是我们直观的表示方式,有时候却是从小到大排。

例如我有两个字节分别是数字2019,假如把它看成一个年份,我们最直观的阅读方式就是2019啦,这就是大端字节序。但假如要用小端字节序的话就是反过来1920,所以字节序没弄清的话会造成很大的差别。

那么怎么判断当前环境是哪个字节序呢?这其实很简单,但关于相关语法后面再讲解。

// 新建一个多字节的缓冲,用 Uint16Array 和 Uint32Array 都可以
var a = new Uint32Array([0x12345678]);

// 再以Uint8Array查看排列顺序
// 假如是大端字节序就会是:[12, 34, 56, 78]
// 假如是小端字节序就会是:[78, 56, 34, 12]
var b = new Uint8Array(a.buffer);
var BigEndian = b[0] == 0x12;

数字和 Unicode 互转

关于 ASCII,Unicode 和 UTF-8 可以网上查一下,了解一下

实际场景中,二进制数据不单单只返回数字,其中还可以返回字符串,这里简单说下数字和Unicode字符串的互转,以后肯定会用到。

// 数字转Unicode
String.fromCharCode.call(String, 20013) === "中";

// Unicode转数字
String.prototype.charCodeAt.call("中", 0) === 20013;
// 或者
"中".charCodeAt(0) === 20013;

Base64

Base64在数据保存和传输也经常用到,有了以上基础,再看Base64原理时就容易得多了。

参考了阮一峰的Base64 笔记

所谓 Base64,就是说选出 64 个字符----小写字母 a-z、大写字母 A-Z、数字 0-9、符号"+"、"/"(再加上作为垫字的"=",实际上是 65 个字符)----作为一个基本字符集。然后,其他所有符号都转换成这个字符集中的字符。

  1. 将每三个字节作为一组,一共是 24 个二进制位。
  2. 将这 24 个二进制位分为四组,每个组有 6 个二进制位。
  3. 在每组前面加两个 00,扩展成 32 个二进制位,即四个字节。
  4. 根据下表,得到扩展后的每个字节的对应符号,这就是 Base64 的编码值。

因为,Base64 将三个字节转化成四个字节,因此 Base64 编码后的文本,会比原文本大出三分之一左右。

要注意一点,常见的Base64字符串前面都有数据类型和编码方式,例如data:image/png;base64,,这部分是给服务器或者浏览器识别的,并不是Base64的一部分,编码时要区分出来。

浏览器自带的Base64编码和反编码方法:

btoa("ABC") === "QUJD";
atob("QUJD") === "ABC";

Nodejs 自带的Base64编码和反编码方法:

Buffer.from("ABC").toString("base64") === "QUJD";
Buffer.from("QUJD", "base64").toString("ascii") === "ABC";