base64

base64

有些协议是文本协议,但是需要传输二进制数据。base64正是将二进制数据转换为可显示字符集的一种编码。

思想

标准ascii中127个字符,[0-31]和127是不可显式的字符。那么[32-126]这个区间,94个字符是可以显示的。
base64只选用了其中的64个字符,为什么选用64个呢,这个算法实现是有关联的。字符肯定和数据是联系起来的,为了能表示64个字符,需要6位的二进制数据,程序中是以字节存储的数据的,也就是数据至少是8的倍数,所以取两者最小公倍数24位,3个字节为单位,将这3个字节分为4部分6个二进制数据,按顺序取出前两位补0凑齐一个字节,那么原来的三个字节的二进制数据就会转换为4个字节的可现实字符数据。
image
如果二进制数据的字节数正好是3的倍数当然后,但是可能多出来1或2个字节。首先向二进制数据中1位1位的补0,直到剩下的二进制位数满足6的倍数,此时再将6位二进制数据按照上述思想转换为1个字节数据。转换完后,肯定不足4个字节,算法会用’='占位符补齐。下面的图一剩余1个字节为例,剩余2个是同样的道理。如果你把剩余两个字节按照下面的图推理一下会发现剩余1个的字节时补两个占位符,剩余两个字节的时补一个占位符。
image

编码

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。微信公众号:后台技术栈。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值