正常情况下咱们可以用Base64将字节数组转成可打印字符串,
但是有的时候咱们需要编码后具有一定的保密性,很明显Base64就不适用了,网上有个与熊论道就挺有意思的,
于是我也研究学习了下,自己实现了一个将字节流编码为可打印(可拷贝)中文字符串的功能,反之,也能将中文字符串解码为原始字节流
具体的思路如下
编码:
1,将字节流从8位一个数值,转成从高位开始14位一个数值
2,由于转换后无法确定最后的有效位的结束位置,特规定编码时,有效位的最后额外加上一位1
3,将新得到的0-16384范围内的数值每个都加上Unicode中文区开始位置0x4E00,这样每个数值就能对应一个中文字符
4,然后就是把数值转成字节流(大端)然后再通过Unicode(大端)编码位中文字符串
解码:
1,将中文字符串根据Unicode(大端)转成字节数组
2,将字节数组中每2个字节转成一个uint16数值(大端)
3,将数值减去0x4E00得到原始数值
4,将原始数值从14位一个转成8位一个的字节数组
代码:
/// <summary> /// 将字节数组编码为可打印的中文字符串 /// </summary> public class BaseHanZi { /// <summary> /// 对字节数组进行编码,返回一个可打印的中文字符串 /// </summary> /// <param name="bs"></param> /// <returns></returns> public string Encode(byte[] bs) { List<int> list = ToNewBitSize(bs.Select(p => (int)p).ToList(), 8, 14); //将字节数组按照14位一个生成数字 List<byte> arr = new List<byte>(); //选择14位是0x4E00加上他不会超出汉字区的范围,15位就超出了,更不要说16位整型 foreach (var p in list) { //将每个数字+Unicode汉字开始区,将每个数字对应一个汉字编码 arr.AddRange(BitConverter.GetBytes((ushort)(p + 0x4E00)).Reverse()); } return Encoding.BigEndianUnicode.GetString(arr.ToArray()); //最后以大端Unicode编码将字节流转成字符串 } /// <summary> /// 对一个可打印的中文字符串进行解码,得到一个字节数组 /// </summary> /// <param name="str"></param> /// <returns></returns> public byte[] Decode(string str) { byte[] bs = Encoding.BigEndianUnicode.GetBytes(str); //将中文字符串以Unicode大端转成字节流 List<int> list = new List<int>(); int i = 0; byte[] temp = new byte[2]; while (i < bs.Length) { //每2个字节转成一个数字 temp[0] = bs[i + 1]; temp[1] = bs[i]; list.Add(BitConverter.ToUInt16(temp, 0) - 0x4E00); //减去Unicode汉字开始位置,得到原始的数值 i += 2; } return ToNewBitSize(list, 14, 8).Select(p => (byte)p).ToArray(); //将14位的数值转成字节流 } /// <summary> /// 将sizeOld比特位的数值转成sizeNew比特位的数值 /// </summary> /// <param name="values"></param> /// <param name="sizeOld"></param> /// <param name="sizeNew"></param> /// <returns></returns> /// <exception cref="Exception"></exception> public List<int> ToNewBitSize(IList<int> values, int sizeOld = 8, int sizeNew = 14) { if (sizeOld == sizeNew) { throw new Exception("长度一致没有意义!"); } if (sizeNew > 16) { throw new Exception("新长度不能超过16!"); } int endMark = 0x80; //结束码,因为变更后无法知道结束位,因此规定编码后必须以0x80即0b_1000_0000结尾 if (sizeOld == 8) { values.Add(endMark); //编码时加入结束码 } int numNew = 0; int size = 0; //新数据当前的长度 int index = 0; //旧数据数组的转换进度索引 List<int> list = new List<int>(); int mark = 0xFFFF >> (16 - sizeOld); //旧数据的掩码,用来清除移位后超出的高位 while (index < values.Count) { int len = sizeOld; //旧数据可用长度 int numOld = values[index]; //旧数据 while (len > 0) { int length = sizeNew - size; //新数据需要的长度 if (length >= len) { length = len; } numNew <<= length; //左移留空间 numNew |= (numOld >> (sizeOld - length)); //将旧数据右移得到指定长度的高位数据并放到新数据中 size += length; len -= length; if (len > 0) { numOld = (numOld << length) & mark; //如果旧数据没用完,移位调整其高位,方便下次使用 } if (size == sizeNew) { list.Add(numNew); numNew = 0; size = 0; } } index++; } if (size > 0) { numNew <<= (sizeNew - size); //最后如果位,需要移位到最左侧,方便处理 list.Add(numNew); } numNew = list[list.Count - 1]; while (numNew == 0) { //因为加了结束码,最后一位不可能为0,移除结束码后面为0的数值 list.RemoveAt(list.Count - 1); numNew = list[list.Count - 1]; } if (sizeOld != 8) { if (numNew == endMark) { //解码时需要移除结束码 list.RemoveAt(list.Count - 1); } } return list; } }View Code
测试:
public void Start() { string str = "开始测试内容123阿斯顿abc测试结束了"; //明文字符串 Console.WriteLine(str); byte[] arr = Encoding.UTF8.GetBytes(str); //原始字节流,开头和结尾都可以有0 BaseHanZi baseHanZi = new BaseHanZi(); string hanzi = baseHanZi.Encode(arr); //编码 Console.WriteLine(hanzi); //中文字符串,看上去像乱码,没人知道是什么鬼 byte[] arr_decode = baseHanZi.Decode(hanzi); //解码 string str_decode = Encoding.UTF8.GetString(arr_decode); //将解码后的字节流转成字符串 Console.WriteLine(str_decode); Console.WriteLine(str == str_decode); //对比是否一致 Console.WriteLine(Convert.ToBase64String(arr)); //base64更简单,但是不具有保密性 }
标签:编码,中文,字节,C#,int,str,字符串 From: https://www.cnblogs.com/luludongxu/p/18001655