base64
有些协议是文本协议,但是需要传输二进制数据。base64正是将二进制数据转换为可显示字符集的一种编码。
思想
标准ascii中127个字符,[0-31]和127是不可显式的字符。那么[32-126]这个区间,94个字符是可以显示的。
base64只选用了其中的64个字符,为什么选用64个呢,这个算法实现是有关联的。字符肯定和数据是联系起来的,为了能表示64个字符,需要6位的二进制数据,程序中是以字节存储的数据的,也就是数据至少是8的倍数,所以取两者最小公倍数24位,3个字节为单位,将这3个字节分为4部分6个二进制数据,按顺序取出前两位补0凑齐一个字节,那么原来的三个字节的二进制数据就会转换为4个字节的可现实字符数据。
如果二进制数据的字节数正好是3的倍数当然后,但是可能多出来1或2个字节。首先向二进制数据中1位1位的补0,直到剩下的二进制位数满足6的倍数,此时再将6位二进制数据按照上述思想转换为1个字节数据。转换完后,肯定不足4个字节,算法会用’='占位符补齐。下面的图一剩余1个字节为例,剩余2个是同样的道理。如果你把剩余两个字节按照下面的图推理一下会发现剩余1个的字节时补两个占位符,剩余两个字节的时补一个占位符。
编码
package base64
import (
"errors"
)
type Base64 struct {
padding rune
encodeMap [64]byte
decodeMap [256]byte
}
var (
stdEncode = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
urlEncode = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
stdPadding rune = '='
rowPadding rune = -1
)
func isShowable(s string) bool {
var i int
for i = 0; i < len(s); i++ {
if s[i] >= 127 || s[i] < 32 {
break
}
}
return i == len(s)
}
func isRepeat(s string) bool {
var count [256]byte
var i int
for i = 0; i < len(s); i++ {
if count[s[i]] != 0 {
break
}
count[s[i]] = 1
}
return i != len(s)
}
func newBase64(data string) *Base64 {
if len(data) != 64 {
panic("the legnth of encode string isn't equal 64")
}
if isRepeat(data) || !isShowable(data) {
panic("the content of encoding string is invalid")
}
ret := &Base64{
padding: stdPadding,
}
for i := 0; i < len(data); i++ {
ret.encodeMap[i] = data[i] // 0:A 1:B 16:Q
}
for i := 0; i < len(data); i++ {
ret.decodeMap[data[i]] = byte(i) // 81(Q):16
}
return ret
}
func (b *Base64) WithPadding(padding rune) error {
if b.decodeMap[padding] != 0 || !isShowable(string(padding)) {
return errors.New("padding is invalid")
}
return nil
}
func (b *Base64) Encode(in []byte) []byte {
if len(in) == 0 {
return nil
}
size := len(in) / 3 * 4
out := make([]byte, size)
si, di := 0, 0
for di < len(out) {
val := uint(in[si])<<16 | uint(in[si+1])<<8 | uint(in[si+2])
out[di+0] = b.encodeMap[val>>18&0x3f]
out[di+1] = b.encodeMap[val>>12&0x3f]
out[di+2] = b.encodeMap[val>>6&0x3f]
out[di+3] = b.encodeMap[val&0x3f]
si += 3
di += 4
}
remain := len(in) % 3
if remain == 0 {
return out
}
val := uint(in[si]) << 16
if remain == 2 {
val |= uint(in[si+1]) << 8
}
out = append(out, b.encodeMap[val>>18&0x3f])
out = append(out, b.encodeMap[val>>12&0x3f])
if remain == 2 {
out = append(out, b.encodeMap[val>>6&0x3f])
}
for i := 0; i < 3-remain; i++ {
out = append(out, byte(b.padding))
}
return out
}
func (b *Base64) Decode(in []byte) []byte {
if len(in) == 0 {
return nil
}
size := len(in) / 4 * 3
out := make([]byte, size)
si, di := 0, 0
for si < len(in) {
val := uint(b.decodeMap[in[si]])<<18 | uint(b.decodeMap[in[si+1]])<<12 | uint(b.decodeMap[in[si+2]])<<6 | uint(b.decodeMap[in[si+3]])
out[di+0] = byte(val >> 16 & 0xff)
out[di+1] = byte(val >> 8 & 0xff)
out[di+2] = byte(val & 0xff)
si += 4
di += 3
}
for i := 0; i < 2; i++ {
if in[len(in)-1] != byte(b.padding) {
break
}
out = out[:len(out)-1]
}
return out
}
var (
Default = newBase64(stdEncode)
)
总结
base32,base16都是同样的道理,主要是这个思路。
关于作者
大四学生一枚,分享数据结构,面试题,golang,C语言等知识。QQ交流群:521625004。微信公众号:后台技术栈。