使用 Nim 语言读写比特流

502 阅读2分钟

Nim编程早茶

Nim 编程实现读写二进制比特流(01串、bits),也就是一个一个比特读写内容。

数据存储

Nim 语言中,数据最小存储单元是字节(bytes),1 个字节包含 8 个比特。无论我们读取文件或者写入文件,都必须以字节为基本单位。Nim 语言中,提供了 streams 标准库,供我们读写字节流。

按比特读写文件

所以说,如果我们希望按 bit 读取文件,我们必须一次读取一个字节,也就是 8 位,然后再对这一个字节中的每一位进行操作。

Nim 中为我们提供了 bitops 模块,供我们操作 bit

我们可以先创建一个 bit 对象。其中 mark 表示目前处于当前字节的第几位,value 是我们读取的字节值。

type
  Bit = object
    mark: int
    value: uint8

按位写入文件

writeBitvalue 参数,是我们写入的 bit 类型。valuetrue,我们向当前位写入 1,反之写入 0。如果 mark 值小于 7,我们就按位填充数值;如果mark 值等于 7,我们就将字节写入文件,并清空 wbit 的数值。要注意,如果写入的比特流不是 8 的倍数,我们在最后一个比特流后面补齐零,然后再将该字节写入文件。

setBitbitops 中的函数,用于将指定位置的比特置为 1。

# Nim 编程早茶
# https://tea.nim-cn.com/archives/
import bitops

var wBit = Bit(mark: 0, value: 0)

proc writeBit(s: Stream, value: bool) =
  if wbit.mark < 7:
    if value:
      setBit(wbit.value, 7 - wbit.mark)
    wbit.mark += 1
  else:
    if value:
      setBit(wbit.value, 7 - wbit.mark)
    s.write(wbit.value)
    wbit.mark = 0
    wbit.value = 0

# ......
if wBit.mark > 0:
  s.write(wbit.value)
  wbit.mark = 0
  wbit.value = 0

按位读取文件

bitops 中为我们提供了 testBit 函数,用于测试当前位是r否为 1,若是,将返回 true

由于写入 bit 的时候,我们有可能进行了补零操作,所以说我们需要提供文本的总长度,才能还原出二进制的原貌。

var rBit = Bit(mark: 0, value: 0)

proc readBit(s: Stream, length: int=0): string = 
  while not s.atEnd:
    rBit.value = s.readUint8
    for i in 0 ..< 8:
      if testBit(rBit.value, 7 - i):
        result &= "1"
      else:
        result &= "0"
  if length != 0:
    return result[0 ..< length]

我们还可以使用,strutils 模块中提供的 toBin 模块,直接将字节转变为二进制数的字符串形式。

import strutils

proc readBit(s: Stream, length: int=0): string = 
  while not s.atEnd:
    rBit.value = s.readUint8
    result &= toBin(int(rBit.value), 8)
  if length != 0:
    return result[0 ..< length]

最后,来看一个完整的例子

import bitops, streams, strutils


type
  Bit = object
    mark: int
    value: uint8

var wBit = Bit(mark: 0, value: 0)
var rBit = Bit(mark: 0, value: 0)

proc writeBit(s: Stream, value: bool) =
  if wbit.mark < 7:
    if value:
      setBit(wbit.value, 7 - wbit.mark)
    wbit.mark += 1
  else:
    if value:
      setBit(wbit.value, 7 - wbit.mark)
    s.write(wbit.value)
    wbit.mark = 0
    wbit.value = 0

proc readBit(s: Stream, length: int=0): string = 
  while not s.atEnd:
    rBit.value = s.readUint8
    result &= toBin(int(rBit.value), 8)
  if length != 0:
    return result[0 ..< length]

# proc readBit(s: Stream, length: int=0): string = 
#   while not s.atEnd:
#     rBit.value = s.readUint8
#     for i in 0 ..< 8:
#       if testBit(rBit.value, 7 - i):
#         result &= "1"
#       else:
#         result &= "0"
#   if length != 0:
#     return result[0 ..< length]


when isMainModule:
  let s = "010110101000101111"
  var input = newFileStream("test_input.txt", fmWrite)
  var length = s.len
  for c in s:
    if c == '1':
      input.writeBit(true)
    else:
      input.writeBit(false)
  if wBit.mark > 0:
    input.write(wbit.value)
    wbit.mark = 0
    wbit.value = 0
  input.close()
  var output = newFileStream("test_input.txt", fmRead)
  let text = readBit(output, length)
  assert s == text
  output.close()