Base64編碼及編碼效能測試
1.什麼是Base64編碼
Base64是一種基於64個可列印字元來表示二進位制資料的表示方法。由於2的6次方等於64,所以每6位為一個單元,對應某個可列印字元。在Base64中的可列印字元包括字母A-Z、a-z、數字0-9,這樣共有62個字元,此外兩個可列印符號在不同的系統中而不同。
2.為什麼要使用Base64編碼
在計算機使用過程中,經常會遇到Binary資料,即一個數據塊,我們知道其首地址和長度,通過指標即可對其進行快速訪問。
但實際環境中,我們又經常需要將這個資料轉換為String字串,而在String字串中有一個規則,遇到'\0'字元便認為字串結束。我們不能保證Binary資料中有沒有'\0'字元,所以直接將Binary資料賦值給String便可能出現問題。
而解決這一問題的辦法就是使用Base64編碼,上面說到Base64編碼只有64個字元,'A-Z' 'a-z' '0-9' + /。沒有'\0'字元,我們只要先辦Binary轉為Base64編碼格式,即可避免出現'\0'字元,從而在賦值給String時變得可靠。
3.編碼規則
二機制資料通過8位表示一個字元,Base64通過6位表示一個字元。所以將一個二進位制資料轉換為Base64編碼,其長度要增加 (lengh*8/6)倍。
其規則如下:
1)依次從Binary資料中取出6位,轉換為一個Base64字元;
2)如果最後有剩餘則以0補夠6位。在補位的同時,需要在Base64結尾以‘=’進行標識,一個'='表示補2個0,兩個'==',表示補4個0。
需要注意的是第二條規則,由於資料本身是8的倍數,所以每次取6位,最後的餘數只能是2或4。這兩個則通過在Base64結尾加‘=’和'=='標識。
下面是一個Base64編碼索引表和補位的說明:
Base64索引表:
Value | Char | Value | Char | Value | Char | Value | Char |
---|---|---|---|---|---|---|---|
0 | A | 16 | Q | 32 | g | 48 | w |
1 | B | 17 | R | 33 | h | 49 | x |
2 | C | 18 | S | 34 | i | 50 | y |
3 | D | 19 | T | 35 | j | 51 | z |
4 | E | 20 | U | 36 | k | 52 | 0 |
5 | F | 21 | V | 37 | l | 53 | 1 |
6 | G | 22 | W | 38 | m | 54 | 2 |
7 | H | 23 | X | 39 | n | 55 | 3 |
8 | I | 24 | Y | 40 | o | 56 | 4 |
9 | J | 25 | Z | 41 | p | 57 | 5 |
10 | K | 26 | a | 42 | q | 58 | 6 |
11 | L | 27 | b | 43 | r | 59 | 7 |
12 | M | 28 | c | 44 | s | 60 | 8 |
13 | N | 29 | d | 45 | t | 61 | 9 |
14 | O | 30 | e | 46 | u | 62 | + |
15 | P | 31 | f | 47 | v | 63 | / |
如果要編碼的位元組數不能被3整除,最後會多出1個或2個位元組,那麼可以使用下面的方法進行處理:先使用0位元組值在末尾補足,使其能夠被3整除,然後再進行base64的編碼。在編碼後的base64文字後加上一個或兩個'='號,代表補足的位元組數。也就是說,當最後剩餘一個八位位元組(一個byte)時,最後一個6位的base64位元組塊有四位是0值,最後附加上兩個等號;如果最後剩餘兩個八位位元組(2個byte)時,最後一個6位的base位元組塊有兩位是0值,最後附加一個等號。 參考下表:
文字(1 Byte) | A | |||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
二進位制位 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | ||||||||||||||||
二進位制位(補0) | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | ||||||||||||
Base64編碼 | Q | Q | ||||||||||||||||||||||
文字(2 Byte) | B | C | ||||||||||||||||||||||
二進位制位 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | x | x | x | x | x | x | ||
二進位制位(補0) | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | x | x | x | x | x | x |
Base64編碼 | Q | k | M |
4.舉例說明
1) 0 1 2編碼為Base64
0 1 2 二進位制表示為 0011 0000 0011 0001 0011 0010
轉換為Base64為 001100 000011 000100 110010
即 12 3 4 50
從編碼表中查詢 M D E y
所以對應的Base64編碼為 "MDEy"
2) 0123編碼為Base64(需要補位)
0 1 2 3 二進位制表示為 0011 0000 0011 0001 0011 0010 0011 0011
轉換為Base64為 001100 000011 000100 110010 001100 110000(後面補4個0)
即 12 3 4 50 12 48
從編碼表中查詢 M D E y M w
所以對應的Base64編碼為 "MDEyMw==" (由於補了4個0,因此需要加上'==')
5.C++程式碼
包括Base64.h 和Base64.cpp 以及一個測試程式 main.cppBase64.h
#ifndef BASE64_H_
#define BASE64_H_
void base64_encode(const char * pData, int len, char * pOut);
void base64_decode(const char * pData, int len, char * pOut);
int calc_base64_len(int data_len);
int calc_data_len(const char * base64, int base64_len);
#endif
Base64.cpp
#include "Base64.h"
int calc_base64_len(int data_len)
{
int last = data_len*8%6;
if(last == 0)
{
return data_len*8/6;
}
else
{
int base64_len = data_len*8/6+1;
base64_len += (last==2?2:1);
return base64_len;
}
}
int calc_data_len(const char * base64, int base64_len)
{
int dec = 0;
while(base64[base64_len-1-dec]== '=')
{
dec++;
}
return ((base64_len-dec)*6)/8;
}
char _getBase64Char(char c)
{
if(c>=0 && c<26)
{
return 'A'+c;
}
else if(c>=26 && c<52)
{
return 'a'+c-26;
}
else if(c>=52 && c<62)
{
return '0'+c-52;
}
else if(c==62)
{
return '+';
}
else if(c==63)
{
return '/';
}
}
char _getCharOfBase64(char c)
{
if(c>='A' && c<='Z')
{
return c-'A';
}
else if(c>='a' && c<='z')
{
return c-'a'+26;
}
else if(c>='0' && c<='9')
{
return c-'0'+52;
}
else if(c=='+')
{
return 62;
}
else if(c=='/')
{
return 63;
}
}
void base64_encode(const char * pData, int len, char * pOut)
{
int index = 0;
while(len-index>=3)
{
const char * p1 = pData+index;
const char * p2 = p1+1;
const char * p3 = p2+1;
char c1 = ((*p1) & (0b11111100)) >>2;
char c2 = ((*p1) & (0b00000011))<<4 | ((*p2) & 0b11110000)>>4;
char c3 = ((*p2) & (0b00001111))<<2 | ((*p3) & 0b11000000)>>6;
char c4 = (*p3) & (0b00111111);
*(pOut++)=_getBase64Char(c1);
*(pOut++)=_getBase64Char(c2);
*(pOut++)=_getBase64Char(c3);
*(pOut++)=_getBase64Char(c4);
index+=3;
}
int last = len-index;
if(last==1)
{
const char * p1 = pData + index;
char c1 = ((*p1) & (0b11111100)) >>2;
char c2 = ((*p1) & (0b00000011))<<4;
*(pOut++)=_getBase64Char(c1);
*(pOut++)=_getBase64Char(c2);
*(pOut++)='=';
*(pOut++)='=';
}
else if(last == 2)
{
const char * p1 = pData+index;
const char * p2 = p1+1;
char c1 = ((*p1) & (0b11111100)) >>2;
char c2 = ((*p1) & (0b00000011))<<4 | ((*p2) & 0b11110000)>>4;
char c3 = ((*p2) & (0b00001111))<<2 ;
*(pOut++)=_getBase64Char(c1);
*(pOut++)=_getBase64Char(c2);
*(pOut++)=_getBase64Char(c3);
*(pOut++)='=';
//*(pOut++)='=';
}
}
void base64_decode(const char * pData, int len, char * pOut)
{
int dec = 0;
while(pData[len-1-dec]== '=')
{
dec++;
}
int real_len = len-dec;
int index=0;
while(real_len-index>=4)
{
const char * p1 = pData+index;
const char * p2 = p1+1;
const char * p3 = p2+1;
const char * p4 = p3+1;
char c1 = _getCharOfBase64(*p1);
char c2 = _getCharOfBase64(*p2);
char c3 = _getCharOfBase64(*p3);
char c4 = _getCharOfBase64(*p4);
*(pOut++)=(c1)<<2 | ((c2)&(0b00110000))>>4;
*(pOut++)=((c2)&(0b00001111))<<4 | ((c3)&(0b00111100))>>2;
*(pOut++)=((c3)&(0b00000011))<<6 | (c4)&(0b00111111);
index+=4;
}
int last = real_len-index;
if(last == 2)
{
const char * p1 = pData+index;
const char * p2 = p1+1;
char c1 = _getCharOfBase64(*p1);
char c2 = _getCharOfBase64(*p2);
*(pOut++)=(c1)<<2 | ((c2)&(0b00110000))>>4;
}
else if(last ==3)
{
const char * p1 = pData+index;
const char * p2 = p1+1;
const char * p3 = p2+1;
char c1 = _getCharOfBase64(*p1);
char c2 = _getCharOfBase64(*p2);
char c3 = _getCharOfBase64(*p3);
*(pOut++)=(c1)<<2 | ((c2)&(0b00110000))>>4;
*(pOut++)=((c2)&(0b00001111))<<4 | ((c3)&(0b00110000))>>2;
}
}
main.cpp
#include <stdio.h>
#include "time.h"
#include "Base64.h"
const char * file_in = "c:\\temp\\1.jpg";
const char * file_mid = "c:\\temp\\1.base64";
const char * file_out="c:\\temp\\11.jpg";
int main(int argc, char **argv)
{
char * pData;
int len;
FILE * fp = fopen(file_in,"rb");
if(fp)
{
fseek(fp,0,SEEK_END);
len = ftell(fp);
fseek(fp,0,SEEK_SET);
pData = new char[len];
fread(pData,1,len,fp);
fclose(fp);
}
int base64_len = calc_base64_len(len);
//int base64_len = len*8/6+1;
//base64_len += (len*8%6)/2; //'='
char * pOut = new char[base64_len];
//encodeBase64(pData,len,pOut);
base64_encode(pData,len,pOut);
/*
clock_t begin,end;
begin = clock();
int loop=0;
while(loop<1000)
{
base64_encode(pData,len,pOut);
loop++;
}
end = clock();
printf("time used %d ms.\n",(end-begin));
return 0;
*/
//pOut[base64_len-1]='\0';
fp = fopen(file_mid,"wb");
if(fp)
{
fwrite(pOut,1,base64_len, fp);
fclose(fp);
}
if(pData)
{
delete []pData;
}
if(pOut)
{
delete []pOut;
}
// read
fp = fopen(file_mid,"rb");
if(fp)
{
fseek(fp,0,SEEK_END);
base64_len = ftell(fp);
fseek(fp,0,SEEK_SET);
pData = new char[base64_len];
fread(pData,1,base64_len,fp);
fclose(fp);
}
//int dec = 0;
//while(pData[base64_len-1-dec]== '=')
//{
// dec++;
//}
//len = ((base64_len-dec)*6)/8;
len = calc_data_len(pData,base64_len);
pOut = new char[len];
//decodeBase64(pData,base64_len,pOut);
base64_decode(pData,base64_len,pOut);
fp = fopen(file_out,"wb");
if(fp)
{
fwrite(pOut,1,len,fp);
fclose(fp);
}
if(pData)
{
delete []pData;
}
if(pOut)
{
delete []pOut;
}
return 0;
}
main.cpp讀取c:\temp\1.jpg,進行Base64編碼,將編碼結果寫入c:\temp\1.base64, 然後再從c:\temp\1.base64中讀取,解碼還原,寫入 c:\temp\11.jpg.
6.Base64編碼效能測試
測試環境:普通PC機 i5 3470 3.2GGhz,記憶體4G,可用3.76 ,win7 32位
使用上述程式碼,編譯環境為mingw。
只測試了Base64 encode的效能,encode1000次,取平均時間:
100K大小資料 1.014ms
200K大小資料 1.981ms
300K 2.917ms
500K 4.961ms
1M 10.624ms
總結:Base64編碼耗時與資料大小成線形比例,在資料量不大時耗時很小。
7.總結與改進
1)該測試程式為本人隨手而寫,還有許多改進空間;以此程式進行測試的結果,僅作參考。
2)由於時間有限,未進行Base64解碼的測試。
在這篇文章中進行了改進,encode效率提高8倍左右。