之前在一个项目中遇到了页面之间的跳转需要保留前一个页面的部分信息的问题,需要将对象转化为Base64字符串便于在页面上保存,于是决定自己实现一个。
Base64要求把每3个8位字节转换为4个6位字节(高2位始终为0),然后依次用26个大小写字母,0-9的十个数字以及“+”和“/”一共64个字符来代替。完整的定义可以参见RFC-2045。 由于要编码的字节存在不能被3整除的情况,最后会多出1或者2个字节,这时候就需要用0来补齐,如果多出1个字节,那么则需要补2个字节,如果多出2个字 节,则需要补1个字节以用于编码,在最后编码的字符串中,补多少个字节就在最后添加多少个“=”。代码很简单了,代码如下:
1 private static string Base64Key = 2 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 3 public static string Encode(byte[] bytes) 4 { 5 StringBuilder base64 = new StringBuilder(); 6 if (bytes == null) 7 { 8 throw new ArgumentNullException(); 9 } 10 int count = bytes.Count(); 11 if (count == 0) 12 { 13 throw new ArgumentException(); 14 } 15 int i = 0; 16 for (i = 0; i <= count - 3; i += 3) 17 { 18 int data = 0; 19 data = data | 20 (bytes[i] << 16) | 21 (bytes[i + 1] << 8 ) | 22 bytes[i + 2]; 23 base64.Append(Base64Key[(data >> 18 & 0x3F)]); 24 base64.Append(Base64Key[(data >> 12 & 0x3F)]); 25 base64.Append(Base64Key[(data >> 6 & 0x3F)]); 26 base64.Append(Base64Key[(data & 0x3F)]); 27 } 28 if (count % 3 != 0) 29 { 30 int data = 0; 31 data = data | (bytes[i] << 16); 32 base64.Append(Base64Key[(data >> 18 & 0x3F)]); 33 if (count % 3 == 1) 34 { 35 base64.Append(Base64Key[(data >> 12 & 0x3F)]); 36 base64.Append('='); 37 } 38 else 39 { 40 data = data | (bytes[i + 1] << 8); 41 base64.Append(Base64Key[(data >> 12 & 0x3F)]); 42 base64.Append(Base64Key[(data >> 6 & 0x3F)]); 43 } 44 base64.Append('='); 45 } 46 return base64.ToString(); 47 }
上述方法使用了一个32位的整数的后24位作为缓冲区,将每3个8位字节由高到低位依次填充,然后再从高位到低位依次按每4个6位字节截取,最后根据截取的结果在所编码表中找到编码字符,但是在填充和截取的过程中,却存在多次重复移位操作,下面是改进后的方法:
public static string Encode(byte[] bytes) { StringBuilder base64 = new StringBuilder(); if (bytes == null) { throw new ArgumentNullException(); } int count = bytes.Count(); if (count == 0) { throw new ArgumentException(); } int i = 0; for (i = 0; i <= count - 3; i += 3) { base64.Append(Base64Key[(bytes[i] >> 2 & 0x3F)]); base64.Append(Base64Key[(bytes[i] << 4 & 0x30) | (bytes[i + 1]) >> 4 & 0x0F]); base64.Append(Base64Key[(bytes[i + 1] << 2 & 0x3C) | (bytes[i + 2] >> 6 & 0x03)]); base64.Append(Base64Key[(bytes[i + 2] & 0x3F)]); } if (count % 3 != 0) { byte[] lastGroup = { bytes[i], 0, 0 }; base64.Append(Base64Key[(lastGroup[0] >> 2 & 0x3F)]); if (count % 3 == 1) { base64.Append(Base64Key[((lastGroup[0] << 4 & 0x3C) | (lastGroup[1] >> 4 & 0x03))]); base64.Append('='); } else { lastGroup[1] = bytes[i + 1]; base64.Append(Base64Key[((lastGroup[0] << 4 & 0x30) | (lastGroup[1]) >> 4 & 0x0F)]); base64.Append(Base64Key[((lastGroup[1] << 2 & 0x3C) | (lastGroup[2] >> 6 & 0x03))]); } base64.Append('='); } return base64.ToString(); }
原理很简单,就用编码字节本身进行截取和拼接,这样使得移位次数大幅下降,执行效率得到了提高。
下面是Encode方法的测试用例:
1 [Test] 2 [ExpectedException(typeof(ArgumentNullException))] 3 public void TestArgumentNullBase64Encode() 4 { 5 Base64.Encode(null); 6 } 7 8 [Test] 9 public void TestZeroByteBase64Encode() 10 { 11 Assert.AreEqual( 12 "", 13 Base64.Encode(new byte[] { })); 14 } 15 16 [Test] 17 public void TestOneByteBase64Encode() 18 { 19 byte[] bytes = { 12 }; 20 Assert.AreEqual("DA==", Base64.Encode(bytes)); 21 Assert.AreEqual( 22 Convert.ToBase64String(bytes), 23 Base64.Encode(bytes)); 24 } 25 26 [Test] 27 public voidd TestTwoBytesBase64Encode() 28 { 29 byte[] bytes = { 12, 43 }; 30 Assert.AreEqual("DCs=", Base64.Encode(bytes)); 31 Assert.AreEqual( 32 Convert.ToBase64String(bytes), 33 Base64.Encode(bytes)); 34 } 35 36 [Test] 37 public void TestThreeBytesBase64Encode() 38 { 39 byte[] bytes = { 12, 43, 55 }; 40 Assert.AreEqual("DCs3", Base64.Encode(bytes)); 41 Assert.AreEqual( 42 Convert.ToBase64String(bytes), 43 Base64.Encode(bytes)); 44 }
Base64的解码则是编码的逆操作,是将Base64字符串反编码为byte[]的过程,代码如下:
1 public static byte[] Decode(string s) 2 { 3 if (s == null) 4 { 5 throw new ArgumentNullException(); 6 } 7 StringBuilder sb = new StringBuilder(s); 8 for (int i = 0; i < sb.Length; i++) 9 { 10 if (sb[i] == ' ') 11 { 12 sb.Remove(i, 1); 13 } 14 } 15 if (sb.Length == 0) 16 { 17 return new byte[0]; 18 } 19 if (sb.Length % 4 != 0) 20 { 21 throw new ArgumentException("Invalid length for a Base-64 string."); 22 } 23 int equalMarkCount = 0; 24 int groups = sb.Length / 4; 25 if (sb[sb.Length - 1] == '=') 26 { 27 equalMarkCount++; 28 groups--; 29 } 30 if (sb[sb.Length - 2] == '=') 31 { 32 equalMarkCount++; 33 } 34 35 byte[] bytes = new byte[sb.Length / 4 * 3 - equalMarkCount]; 36 37 int ii = 0; 38 int j = 0; 39 for (int i = 0; i < groups; i++) 40 { 41 //不能有除标准符号中的其他符号 42 int b1 = Base64Key.IndexOf(sb[ii++]); 43 int b2 = Base64Key.IndexOf(sb[ii++]); 44 int b3 = Base64Key.IndexOf(sb[ii++]); 45 int b4 = Base64Key.IndexOf(sb[ii++]); 46 if (b1 == -1 || b2 == -1 || b3 == -1 || b4 == -1) 47 { 48 throw new ArgumentException("Invalid character in a Base-64 string."); 49 } 50 51 /* 52 * 将第1个数左移2位作为原编码字节的高6位 53 * 将第2个数右移4位获取其高2位作为原编码字节的低2位 54 */ 55 bytes[j++] = (byte)((b1 << 2 & 0xFC) | (b2 >> 4 & 0x03)); 56 57 /* 58 * 将第2个数左移4位作为原编码字节的高4位 59 * 将第3个数右移2位获取其高4位作为原编码字节的低4位 60 */ 61 bytes[j++] = (byte)((b2 << 4 & 0xF0) | (b3 >> 2 & 0x0F)); 62 63 /* 64 * 将第3个数左移6位作为原编码字节的低2位 65 * 最后1个数则无需移动 66 */ 67 bytes[j++] = (byte)((b3 << 6 & 0xC0) | (b4 & 0x3F)); 68 } 69 if(equalMarkCount != 0) 70 { 71 int b1 = Base64Key.IndexOf(sb[ii++]); 72 int b2 = Base64Key.IndexOf(sb[ii++]); 73 if (b1 == -1 || b2 == -1) 74 { 75 throw new ArgumentException("Invalid character in a Base-64 string."); 76 } 77 bytes[j++] = (byte)((b1 << 2 & 0xFC) | (b2 >> 4 & 0x03)); 78 if (equalMarkCount == 1) 79 { 80 int b3 = Base64Key.IndexOf(sb[ii++]); 81 bytes[j++] = (byte)((b2 << 4 & 0xF0) | (b3 >> 2 & 0x0F)); 82 } 83 } 84 return bytes; 85 }
PS:博客搬家,原文于2012-02-13发布于 http://lukyw.sinaapp.com/2012/02/13/base64/