首页 > 编程语言 >C# 自己写的编码机制,将任意字节数组和可打印中文字符串相互转换

C# 自己写的编码机制,将任意字节数组和可打印中文字符串相互转换

时间:2024-02-01 17:12:39浏览次数:24  
标签:编码 中文 字节 C# int str 字符串

正常情况下咱们可以用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

相关文章

  • 【C++】力扣101-分配问题和区间问题
    1.有一群孩子和一堆饼干,每个孩子有一个饥饿度,每个饼干都有一个大小。每个孩子只能吃一个饼干,且只有饼干的大小不小于孩子的饥饿度时,这个孩子才能吃饱。求解最多有多少孩子可以吃饱。#include<iostream>#include<vector>#include<algorithm>usingnamespacestd;intcalc......
  • @MappedSuperclass用法,主要用于JPA基类(超类)的定义
    @MappedSuperclass 是JavaPersistenceAPI(JPA)中的一个注解,用于指示某个类是一个映射的超类(MappedSuperclass)。映射的超类类似于普通的Java类,但它不会被映射到数据库表,而是作为其他实体类的基类,用于共享字段和方法。当你在JPA中定义一个实体类的时候,可以使用 @Entity ......
  • Java并发基础:CyclicBarrier全面解析!
    内容摘要CyclicBarrier的优点在于实现了线程间的相互等待与协同,确保所有线程在达到预定屏障点后才能继续执行,它支持屏障的重复使用,非常适合多轮次的任务同步,此外,CyclicBarrier还允许在屏障点执行特定操作,为复杂的多线程协作提供了便利。核心概念业务场景CyclicBarrier允许一组......
  • 1.C语言学习--分支与循环
    1.什么是语句常在一条代码的末尾加一个分号“;”,C语言中分号前的就是一条语句;2.分支语句分支语句又叫选择结构。表示当满足某个条件时,程序可以选择不同的执行路径。包括if语句和switch语句。2.1if语句结构:if(条件)语句(带来的结果);else语句(带来的结果);若满足if括号里面的......
  • C语言学习6
    循环结构1while的循环语句分成初始化,判断,调整流程:break直接终止整个循环continue是跳过本次循环它后面的代码,直接进入下一次循环光标一直在闪,一直在进行死循环函数的一个综合库MSDN补充的一个错误然后该怎么做getchar是获取字符的意思,从哪儿获取,从你打的字符获取上面getchar是直接......
  • 无涯教程-concat()函数
    此方法添加两个或多个字符串,并返回一个新的单个字符串。concat()-语法string.concat(string2,string3[,...,stringN]);string2...stringN  - 这些是要串联的字符串。concat()-返回值返回单个串联的字符串。concat()-示例varstr1=newString("Thisis......
  • 永久修改/etc/proc下的项的配置文件
    一般/proc/下的数据,想永久生效,都需要放在/etc/sysctl.conf如何需要修改如/proc/sys/net/nf_conntrack_max这个的参数,需要将net后面的斜杠换成点后vim/etc/sysctl.confnet.nf_conntrack_max=656666重新生效sysctl-p......
  • 2.C语言学习--分支与循环例题分析
    1.计算n的阶乘intmain(){ intret=1; inti=0; intn=0; scanf("%d",&n);//注意取地址符号&别忘记 for(i=1;i<=n;i++) { ret=ret*i; } printf("ret=%d\n",ret); return0;}效果如下所示:2.计算1!+2!+...+10!intmain(){ ......
  • Python中的基础数据类型:List、Tuple和Dict及其常用用法简析
    在Python编程语言中,基础数据类型是构建程序的基本元素。这些基础数据类型包括List(列表)、Tuple(元组)和Dict(字典)。每种数据类型都有其特定的用途和特性,了解并掌握它们对于编写高效、可维护的Python代码至关重要。本文将深入探讨这三种基础数据类型,并通过代码示例展示它们的常用用法。......
  • Spring的任务执行器(TaskExecutor)入门
    Spring的任务执行器(TaskExecutor)入门在现代的应用程序开发中,异步任务的处理是非常常见的需求。Spring框架提供了任务执行器(TaskExecutor)来处理异步任务,使得开发者能够轻松地实现并发处理和异步操作。本篇博文将介绍Spring的任务执行器,包括其概念、用法和最佳实践。什么是任务执行器......