你知道Base64编码吗?跟我一起用Go语言实现它吧
liebian365 2024-10-24 14:32 5 浏览 0 评论
为什么会有Base64编码呢?
早期的邮件只能处理文本格式的内容,而且是英文格式的内容,为了能在邮件中使用其他语言,同时可以支持传输图片、音频、视频等多媒体内容,人们设计扩展了邮件传输协议,使得它能支持一种叫做MIME(Multipurpose Internet Mail Extentions,多媒体邮件扩展标准)标准。
在MIME中,为了能够使用文本协议传输这部分内容,就需要对传输的对象进行转码。如果不进行转码,这部分数据是无法直接在邮件传输的。一个很重要的原因是邮件传输协议使用.符号作为正文的结束符,直接传输二进制的数据会导致可能出现误判,就无法获取到完整的邮件内容。
MIME中最常用的转码方式有两种,对于含有大量7BIT字符的数据多采用Quoted-Printable编码,对于二进制的数据多使用Base64编码。
Base64编码的原理是怎样的?
Base64编码就是把二进制的数据通过一个映射关系,全部转换为64个预定义的字符,这64个字符中不包含控制符,所以能够被邮件等正确的传输识别。
这64个字符的码表索引定义如下图所示:
那二进制的数据是怎样转变成上述的码表中的字符呢?
通常,计算机中传输数据都是以字节(一般为8bit)为单位,我们需要对要进行转码的数据以每3个字节分组,也就是24bit一组,将这24bit拆分成4份,每份6bit,每6bit组成一个新的字节(前两位不足的补足为0),这样就能得到n组4字节的数据。
由于拆分后的每个字节实际只有6位有实意,也就是最大只能表示64,于是就可以通过以上的码表转换成对应的字符,然后们就可以通过转换后的字符进行传输了。
到这里可能有的同学会问,上面直接拆分成4字节后直接传输不可以吗?为什么还要再进行码表转换呢?这是由于虽然拆分成了4字节,但是其数据还是包含控制字符,例如39也就是ascii字符中的.号,这个是邮件传输协议中的正文结束符,所以还是不满足要求,只要转换成上面码表中的字符后,才能被正确的识别。
同时,还有同学会问,我们要传输的数据可能并不是3的整数倍,这时候要怎么办呢?对于不是3的整数倍的数据,我们先填充空字符,也就是二进制为 00000000,记录下差几个字符,按上述方法转换完成后,将最后差的字符替换为=号,比如差1个字符,就将最后一位替换为=,差两位就将最后两位替换为==。当然,最大也就只会差两位,至于为什么,可以自己思考下~
这里我们以编码hello这个字符串为例:
根据编码的原理,我们也可以很容易的知道解码的原理。
用Go语言怎么去实现?
对于编码而言,首先我们需要定义一个码表,这里码表中的数字是对应的ascii码。
var table = []byte{
65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79, 80,
81, 82, 83, 84, 85, 86, 87, 88,
89, 90, 97, 98, 99, 100, 101, 102,
103, 104, 105, 106, 107, 108, 109, 110,
111, 112, 113, 114, 115, 116, 117, 118,
119, 120, 121, 122, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 43, 47,
}
其实,就是类似如下:
var table = []string{
"A", "B", "C", "D", "E", "F", "G", "H",
"I", "J", "K", "L", "M", "N", "O", "P",
"Q", "R", "S", "T", "U", "V", "W", "X",
"Y", "Z", "a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n",
"o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "0", "1", "2", "3",
"4", "5", "6", "7", "8", "9", "+", "/",
}
我们还定义了一个变量,用于保存缺失字符的替换字符=符号,61是=符号的ascii码:
var byteEq byte = 61
编码的方法:
func Encode(bytes []byte) string {
bytesLen := len(bytes)
// 计算缺失的位,即不满足以3字节分组的位数
bytesMiss := 0
if bytesLen%3 != 0 {
bytesMiss = 3 - bytesLen%3
}
// 以空字符填充不足位
if bytesMiss != 0 {
for i := 0; i < bytesMiss; i++ {
bytes = append(bytes, 0b00000000)
}
bytesLen += bytesMiss
}
// 分组,编码
groupCount := bytesLen / 3
// 申明一个原始的分组和一个对应新编码索引的分组
oriGroup := make([]byte, 3)
newGroupIndex := make([]byte, 4)
// 申明转换后的变量
newBytes := make([]byte, bytesLen/3*4)
// 分组执行
for i := 0; i < groupCount; i++ {
// 获得当前这个组的字节码
oriGroup = bytes[i*3 : i*3+3]
// 获取对应的索引位置
newGroupIndex[0] = oriGroup[0] >> 2
newGroupIndex[1] = (oriGroup[0] & 0b00000011 << 4) | (oriGroup[1] & 0b11110000 >> 4)
newGroupIndex[2] = (oriGroup[1] & 0b00001111 << 2) | (oriGroup[2] & 0b11000000 >> 6)
newGroupIndex[3] = oriGroup[2] & 0b00111111
// 转换,其实上面的oriGroup和newGroup两个变量都没必要,这里只是为了方便理解专门加上的,可以直接用索引定位
newBytes[i*4] = table[newGroupIndex[0]]
newBytes[i*4+1] = table[newGroupIndex[1]]
newBytes[i*4+2] = table[newGroupIndex[2]]
newBytes[i*4+3] = table[newGroupIndex[3]]
}
// 如有缺失位,进行替换,替换为=符号
if bytesMiss != 0 {
for i := 1; i <= bytesMiss; i++ {
newBytes[len(newBytes)-i] = byteEq
}
}
return string(newBytes)
}
顺便,我们也实现了解码的方法。
首先,我们需要定义一个码表对应的索引,key是字符的ascii码:
var tableMap = map[byte]byte{
65: 0, 66: 1, 67: 2, 68: 3, 69: 4, 70: 5, 71: 6, 72: 7,
73: 8, 74: 9, 75: 10, 76: 11, 77: 12, 78: 13, 79: 14, 80: 15,
81: 16, 82: 17, 83: 18, 84: 19, 85: 20, 86: 21, 87: 22, 88: 23,
89: 24, 90: 25, 97: 26, 98: 27, 99: 28, 100: 29, 101: 30, 102: 31,
103: 32, 104: 33, 105: 34, 106: 35, 107: 36, 108: 37, 109: 38, 110: 39,
111: 40, 112: 41, 113: 42, 114: 43, 115: 44, 116: 45, 117: 46, 118: 47,
119: 48, 120: 49, 121: 50, 122: 51, 48: 52, 49: 53, 50: 54, 51: 55,
52: 56, 53: 57, 54: 58, 55: 59, 56: 60, 57: 61, 43: 62, 47: 63,
}
其实就是类似如下:
var tableMap = map[string]byte {
"A": 0, "B": 1, "C": 2, "D": 3, "E": 4, "F": 5, "G": 6, "H": 7,
"I": 8, "J":9 , "K":10, "L":11, "M":12, "N":13, "O":14, "P":15,
"Q":16, "R":17, "S":18, "T":19, "U":20, "V":21, "W":22, "X":23,
"Y":24, "Z":25, "a":26, "b":27, "c":28, "d":29, "e":30, "f":31,
"g":32, "h":33, "i":34, "j":35, "k":36, "l":37, "m":38, "n":39,
"o":40, "p":41, "q":42, "r":43, "s":44, "t":45, "u":46, "v":47,
"w":48, "x":49, "y":50, "z":51, "0":52, "1":53, "2":54, "3":55,
"4":56, "5":57, "6":58, "7":59, "8":60, "9":61, "+":62, "/":63,
}
解码的代码如下:
func Decode(bytes []byte) (string, error) {
// 如果不能被4整除,说明不是正常的base64编码后的数据
if len(bytes)%4 > 0 {
return "", errors.New("not valid base64 code")
}
// 统计缺失的位
byteMiss := 0
var ok bool
// 只有最后才有缺失字符转换码=,这里定义一个标志来进行判断
var missLock bool
for i := len(bytes) - 1; i >= 0; i-- {
if bytes[i] == byteEq {
if missLock {
return "", errors.New("only the end could have =")
}
byteMiss += 1
bytes[i] = 0
} else {
missLock = true
// 判断是否是在码表中的字符,并转换成对应的索引
bytes[i], ok = tableMap[bytes[i]]
if !ok {
return "", errors.New("not a valid base64 byte")
}
}
}
// 缺失位不能超过2个
if byteMiss > 2 {
return "", errors.New("byte miss more than 2")
}
// 定义一个原始组合一个新的组,进行转换
oriGroup := make([]byte, 4)
newGroup := make([]byte, 3)
groupCount := len(bytes) / 4
newBytes := make([]byte, len(bytes)/4*3)
for i := 0; i < groupCount; i++ {
// 获取到原始组的字节码
oriGroup = bytes[i*4 : i*4+4]
// 根据规则计算
newGroup[0] = (oriGroup[0] << 2) | (oriGroup[1] & 0b00110000 >> 4)
newGroup[1] = (oriGroup[1] & 0b00001111 << 4) | (oriGroup[2] & 0b00111100 >> 2)
newGroup[2] = (oriGroup[2] & 0b00000011 << 6) | oriGroup[3]
newBytes[3*i] = newGroup[0]
newBytes[3*i+1] = newGroup[1]
newBytes[3*i+2] = newGroup[2]
}
// 如果有缺失位,
if byteMiss > 0 {
newBytes = newBytes[0:len(newBytes) - byteMiss]
}
return string(newBytes), nil
}
以上,我们就用Go语言实现了一个Base64的编解码的的包,我们可以很方便的调用里面的编解码方法来实现对数据的Base64编解码。
相关推荐
- 快递查询教程,批量查询物流,一键管理快递
-
作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...
- 一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递
-
对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...
- 快递查询单号查询,怎么查物流到哪了
-
输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...
- 3分钟查询物流,教你一键批量查询全部物流信息
-
很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...
- 快递单号查询,一次性查询全部物流信息
-
现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...
- 快递查询工具,批量查询多个快递快递单号的物流状态、签收时间
-
最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...
- 快递查询软件,自动识别查询快递单号查询方法
-
当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...
- 教你怎样查询快递查询单号并保存物流信息
-
商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...
- 简单几步骤查询所有快递物流信息
-
在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...
- 物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号
-
最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...
- 连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息
-
快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...
- 快递查询教程,快递单号查询,筛选更新量为1的单号
-
最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...
- 掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析
-
在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...
- 从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息
-
在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...
- 物流单号查询,在哪里查询快递
-
如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)