使用C#处理基于比特流的数据

0x00 起因

最近需要处理一些基于比特流的数据,计算机处理数据一般都是以byte(8bit)为单位的,使用BinaryReader读取的数据也是如此,即使读取bool型也是一个byte。不过借助于C#基础类库中提供的一些方法,也实现了对基于比特的数据的读取。任务完成后觉得基于比特的数据挺有意思,自己试了下用7比特和6比特编码常用ASCII字符。最后把一点新的写成博客,一方面做个记录,另一方面希望对有类似需求的园友有所帮助。

0x01 比特流数据的读取

假设我们有一个byte b = 35,而我们需要把其中的前4bit和后4bit分别读取为两个数字,那么应该怎么做呢。虽然没有在基础类库中找到现成的方法,但用二进制字符串中转一下,分两步也可以做到。

1、先把b表示为二进制字符串00100011

2、分别取其前后4bit转为数字,核心方法就是:

Convert.ToInt32("");

这样就实现了基于比特的数据读取了。

关于第一步中把byte转化为二进制字符串有很多种方法,

1、最简单的Convert.ToString(b,2)。不够8位就在高位用0补足。

2、也可以把byte分别与1,2,4,8 … 128做与运算,由低到高取出各位。

3、也可以把byte和32做与运算,然后把byte左移再次与128做与运算。

其中第一种方法会产生大量的字符串对象,在第2、3种方法没有找到太大区别,我选择的3,纯靠感觉。代码如下:

public static char[] ByteToBinString(byte b)
{
  var result = new char[];
  for (int i = ; i < ; i++)
  {
    var temp = b & ;
    result[i] = temp == ? '' : '';
    b = (byte)(b << );
  }
  return result;
}

为了能将byte[]转化为二进制字符串,可以

Public string BitReader(byte[] data)
{
BinString = new StringBuilder(data.Length * );
for (int i = ; i < data.Length;
{
BinString.Append(ByteToBinString(data[i]));
}
return BinString.ToString();
}

这样一来当拿到byte[]数据时,可以转换为二进制字符串保存起来,根据偏移的bit位置和bit长度从中读取二进制字符串,并保转换为bool,Int16,Int32等。基于这个思路,可以写一个BitReader类,其中用StringBuilder存储二进制字符串,并提供Read方法从二进制字符串中读取数据。为了能够更好的处理数据流,在此基础上添加一个Position记录当前偏移,当使用某些Read方法读取数据时,Position也会相应移动。例如使用ReadInt16读取数据,BitReader会从Position当前位置,读取16bit并转换为Int16返回,同时Position向后移动16bit。区分方式就是当读取数据时需要指定起始的偏移位置时,Position不移动,直接从当前Position读取时Position移动,BitReader类部分代码如下:

 public class BitReader
{
public readonly StringBuilder BinString;
public int Position { get; set; } public BitReader(byte[] data)
{
BinString = new StringBuilder(data.Length * );
for (int i = ; i < data.Length; i++)
{
BinString.Append(ByteToBinString(data[i]));
}
Position = ;
} public byte ReadByte(int offset)
{
var bin = BinString.ToString(offset, );
return Convert.ToByte(bin, );
} public byte ReadByte()
{
var result = ReadByte(Position);
Position += ;
return result;
} public int ReadInt(int offset, int bitLength)
{
var bin = BinString.ToString(offset, bitLength);
return Convert.ToInt32(bin, );
} public int ReadInt(int bitLength)
{
var result = ReadInt(Position, bitLength);
Position += bitLength;
return result;
} public static char[] ByteToBinString(byte b)
{
var result = new char[];
for (int i = ; i < ; i++)
{
var temp = b & ;
result[i] = temp == ? '' : '';
b = (byte)(b << );
}
return result;
}
}

使用BitReader按照4bit从byte[] buff= {35,12};中读取数据可以这样:

var reader = new BitReader(buff); //二进制字符串为0010001100001100

var num1 = reader.ReadInt();   //从当前Position读取4bit为int,Position移动4bit,结果为2,当前Position=4

var num2 = reader.ReadInt(,);  //从偏移为5bit的位置读取6bit为int,Position不移动,结果为48,当前Position=4

var b = reader.ReadBool();  //从当前Position读取1bit为bool,Position移动1bit,结果为False,当前Position=5

0x02 比特流数据的写入

把数据写入比特流就是一个相反的过程,我们用BitWriter类实现,在其中存储StringBuilder保存二进制字符串,当写入数据时,需要传入数据并指定保存这个数据所需要的bit数。当写入完毕后可以将StringBuilder中保存的二进制字符串按照8bit转换为byte[]并返回。BitWriter的核心部分如下:

 public class BitWriter
{
public readonly StringBuilder BinString; public BitWriter()
{
BinString = new StringBuilder();
} public BitWriter(int bitLength)
{
var add = - bitLength % ;
BinString = new StringBuilder(bitLength + add);
} public void WriteByte(byte b, int bitLength=)
{
var bin = Convert.ToString(b, );
AppendBinString(bin, bitLength);
} public void WriteInt(int i, int bitLength)
{
var bin = Convert.ToString(i, );
AppendBinString(bin, bitLength);
} public void WriteChar7(char c)
{
var b = Convert.ToByte(c);
var bin = Convert.ToString(b, );
AppendBinString(bin, );
} public byte[] GetBytes()
{
Check8();
var len = BinString.Length / ;
var result = new byte[len]; for (int i = ; i < len; i++)
{
var bits = BinString.ToString(i * , );
result[i] = Convert.ToByte(bits, );
} return result;
} public string GetBinString()
{
Check8();
return BinString.ToString();
} private void AppendBinString(string bin, int bitLength)
{
if (bin.Length > bitLength)
throw new Exception("len is too short");
var add = bitLength - bin.Length;
for (int i = ; i < add; i++)
{
BinString.Append('');
}
BinString.Append(bin);
} private void Check8()
{
var add = - BinString.Length % ;
for (int i = ; i < add; i++)
{
BinString.Append("");
}
}
}

下面举个简单的例子:

var writer = new BitWriter();

writer.Write(,);  //把12用5bit写入,此时二进制字符串为:01100

writer.Write(,);  //把8用16bit写入,此时二进制字符串为:011000000000000001000

var result = writer.GetBytes(); //8bit对齐为011000000000000001000000
//返回结果为[96,0,64]

0x03 7比特字符编码

我们常用的ASCII字符是使用8bit编码的,但其中真正常用的那些字符只有7bit,最高位为0,所以对于一篇英文文章,我们可以使用7bit重新编码而不损失信息。编码的过程就是把文章字符依次取出,并用BitWriter按照7bit写入,最后获取新编码的byte[]。为了能够正确读取,我们规定当读到8bit数据为2时代表数据开始,接下来16bit数据为后面字符个数。代码如下:

    public byte[] Encode(string text)
{
var len = text.Length * + ; var writer = new BitWriter(len);
writer.WriteByte();
writer.WriteInt(text.Length, ); for (int i = ; i < text.Length; i++)
{
var b = Convert.ToByte(text[i]);
writer.WriteByte(b, );
} return writer.GetBytes();
}

同样读取数据的时候,我们先寻找开始标识符,然后读出字符个数,根据字符个数依次读取字符,代码如下:

    public string Decode(byte[] data)
{
var reader = new BitReader(data);
while (reader.Remain > )
{
var start = reader.ReadByte();
if (start == )
break;
}
var len = reader.ReadInt();
var result = new StringBuilder(len);
for (int i = ; i < len; i++)
{
var b = reader.ReadInt();
var ch = Convert.ToChar(b);
result.Append(ch);
} return result.ToString();
}

由于数据头的存在,当编码几个字符时编码后数据反而更长了

不过随着字符越多,编码后节省的越多。

0x04 6比特字符编码

从节省数据量的角度,如果允许损失部分信息,例如损失掉字母大小写,是可以进一步减少编码所需比特数的。26个字母+10个数字+符号,可以用6bit(64)进行编码。不过使用这种编码方式就不能用ASCII的映射方式了,我们可以自定义映射,例如0-10映射为十个数字等等,也可以使用自定义的字典,也就是传说中的密码本。经常看国产谍战片的应该都知道密码本吧,密码本就是一个字典,把字符进行重新映射获取明文,算是简单的单码替代,加密强度很小,在获取足量数据样本后基于统计很容易就能破解。下面我们就尝试基于自定义字典用6bit重新编码。

编码过程:

仍然像7bit编码那样写入消息头,然后依次取出文本中的字符,从字典中找到对应的数字,把数字按照6bit长度写入到BitWriter

    public byte[] Encode(string text)
{
text = text.ToUpper();
var len = text.Length * + ; var writer = new BitWriter(len);
writer.WriteByte();
writer.WriteInt(text.Length, ); for (int i = ; i < text.Length; i++)
{
var index = GetChar6Index(text[i]);
writer.WriteInt(index, );
} return writer.GetBytes(); } private int GetChar6Index(char c)
{
for (int i = ; i < ; i++)
{
if (Dict.Custom[i] == c)
return i;
}
return ; //return *
}

解码过程:

解码也很简单,找到消息头,依次按照6bit读取数据,并从字典中找到对应的字符:

public string Decode(byte[] data)
{
var reader = new BitReader(data);
while(reader.Remain > )
{
var start = reader.ReadByte();
if (start == )
break;
}
var len = reader.ReadInt();
var result = new StringBuilder(len);
for (int i = ; i < len; i++)
{
var index = reader.ReadInt();
var ch = Dict.Custom[index];
result.Append(ch);
} return result.ToString();
}

同样一段文本用6bit自定义字典编码后数据长度更短了,不过损失了大小写和换行等格式。

如果从加密的角度考虑,可以设置N个自定义字典(假设10个),在消息头中用M bit(例如4bit)表示所用的字典。这样在每次编码时随机选择一个字典编码,解码时根据4bit数据选择相应字典解码,并且定时更换字典可以增大破解难度。感兴趣的园友可以自行尝试。

0x05 写在最后

以上是我处理比特流数据的一点心得,仅仅是我自己能想到的一种方法,满足了我的需求。如果有更效率的更合理的方法,希望赐教。另外编码和解码的两个例子是出于有趣写着玩的,在实际中估计也用不到。毕竟现在带宽这么富裕,数据加密也有N种可靠的多的方式。

示例代码:https://github.com/durow/TestArea/tree/master/BitStream

关于基于比特流的数据读取封装成了库

安装:PM> Install-Package Ayx.BitIO

项目地址:https://github.com/durow/Ayx.BitIO


更多内容欢迎访问我的博客:http://www.durow.vip

使用C#处理基于比特流的数据的更多相关文章

  1. 字节跳动流式数据集成基于Flink Checkpoint两阶段提交的实践和优化

    背景 字节跳动开发套件数据集成团队(DTS ,Data Transmission Service)在字节跳动内基于 Flink 实现了流批一体的数据集成服务.其中一个典型场景是 Kafka/ByteM ...

  2. xilinx Vivado的使用详细介绍(2):创建工程、添加文件、综合、实现、管脚约束、产生比特流文件、烧写程序、硬件验证

    xilinx Vivado的使用详细介绍(2):创建工程.添加文件.综合.实现.管脚约束.产生比特流文件.烧写程序.硬件验证 Author:zhangxianhe 新建工程 打开Vivado软件,直接 ...

  3. 【C#IO 操作】stream 字节流|字符流 |比特流

    stream的简介 Stream 所有流的抽象基类. 流是字节序列的抽象,例如文件.输入/输出设备.进程中通信管道或 TCP/IP 套接字. Stream类及其派生类提供这些不同类型的输入和输出的一般 ...

  4. 大数据实时处理-基于Spark的大数据实时处理及应用技术培训

    随着互联网.移动互联网和物联网的发展,我们已经切实地迎来了一个大数据 的时代.大数据是指无法在一定时间内用常规软件工具对其内容进行抓取.管理和处理的数据集合,对大数据的分析已经成为一个非常重要且紧迫的 ...

  5. html5-websocket实现基于远程方法调用的数据交互

    html5-websocket实现基于远程方法调用的数据交互   一般在传统网页中注册用户信息都是通过post或ajax提交到页面处理,到了HTML5后我们有另一种方法就是通过websocket进行数 ...

  6. 应用层级时空记忆模型(HTM)实现对实时异常流时序数据检测

    应用层级时空记忆模型(HTM)实现对实时异常流时序数据检测 Real-Time Anomaly Detection for Streaming Analytics Subutai Ahmad SAHM ...

  7. 软工之词频统计器及基于sketch在大数据下的词频统计设计

    目录 摘要 算法关键 红黑树 稳定排序 代码框架 .h文件: .cpp文件 频率统计器的实现 接口设计与实现 接口设计 核心功能词频统计器流程 效果 单元测试 性能分析 性能分析图 问题发现 解决方案 ...

  8. ASP.NET Core MVC中Controller的Action如何直接使用Response.Body的Stream流输出数据

    在ASP.NET Core MVC中,我们有时候需要在Controller的Action中直接输出数据到Response.Body这个Stream流中,例如如果我们要输出一个很大的文件到客户端浏览器让 ...

  9. 【MATLAB】十进制字节矩阵与比特流矩阵的互相转化

    for i=1:length(enc_out_data) data_bits_temp=dec2bin(enc_out_data(i),8); databits((i-1)*8+1:i*8)=doub ...

随机推荐

  1. ASP.NET Core 中间件之压缩、缓存

    前言 今天给大家介绍一下在 ASP.NET Core 日常开发中用的比较多的两个中间件,它们都是出自于微软的 ASP.NET 团队,他们分别是 Microsoft.AspNetCore.Respons ...

  2. .NET Core 首例 Office 开源跨平台组件(NPOI Core)

    前言 最近项目中,需要使用到 Excel 导出,找了一圈发现没有适用于 .NET Core的,不依赖Office和操作系统限制的 Office 组件,于是萌生了把 NPOI 适配并移植到 .NET C ...

  3. SQL:指定名称查不到数据的衍伸~空格 换行符 回车符的批量处理

    异常处理汇总-数据库系列  http://www.cnblogs.com/dunitian/p/4522990.html 先看看啥情况 复制查询到的数据,粘贴一下看看啥情况 那就批量处理一下~ 就这样 ...

  4. SQL必备知识点

    经典SQL语句大全 基础 1.说明:创建数据库.说明:删除数据库drop database dbname3.说明:备份sql server--- 创建 备份数据的 device.说明:创建新表crea ...

  5. 使用python抓取婚恋网用户数据并用决策树生成自己择偶观

    最近在看<机器学习实战>的时候萌生了一个想法,自己去网上爬一些数据按照书上的方法处理一下,不仅可以加深自己对书本的理解,顺便还可以在github拉拉人气.刚好在看决策树这一章,书里面的理论 ...

  6. 算法与数据结构(七) AOV网的拓扑排序

    今天博客的内容依然与图有关,今天博客的主题是关于拓扑排序的.拓扑排序是基于AOV网的,关于AOV网的概念,我想引用下方这句话来介绍: AOV网:在现代化管理中,人们常用有向图来描述和分析一项工程的计划 ...

  7. 操作系统篇-hello world(免系统运行程序)

     || 版权声明:本文为博主原创文章,未经博主允许不得转载. 一.前言     今天起开始分享关于操作系统的相关知识,本人也是菜鸟一个,正处于学习阶段,这整个操作系统篇也是我边学习边总结的一些结果,希 ...

  8. 验证管理员权限(C#)

    参考页面: http://www.yuanjiaocheng.net/webapi/test-webapi.html http://www.yuanjiaocheng.net/webapi/web-a ...

  9. Impress.js上手 - 抛开PPT、制作Web 3D幻灯片放映

    前言: 如果你已经厌倦了使用PPT设置路径.设置时间.设置动画方式来制作动画特效.那么Impress.js将是你一个非常好的选择. 用它制作的PPT将更加直观.效果也是嗷嗷美观的. 当然,如果用它来装 ...

  10. (一)Spark简介-Java&Python版Spark

    Spark简介 视频教程: 1.优酷 2.YouTube 简介: Spark是加州大学伯克利分校AMP实验室,开发的通用内存并行计算框架.Spark在2013年6月进入Apache成为孵化项目,8个月 ...