在 Common LISP 中更改字节宽度中流

Changing byte-width midstream in Common LISP

假设我有一些实用函数定义为:

(defun write-bytes-to-file (bytes file-path bits)
  (with-open-file (stream file-path
                           :direction :output
                           :if-does-not-exist :create
                           :if-exists :append
                           :element-type (list 'unsigned-byte bits))
     (dolist (b bytes))
       (write-byte b stream))))

(defun read-file-bytes-to-list (file-path bits)
  (with-open-file (stream file-path :direction :input :element-type (list 'unsigned-byte bits))
     (read-bytes-to-list stream nil)))

此外,假设我 运行:

(write-bytes-to-file '(65 65 65) "foo.txt" 8)
(write-bytes-to-file '(512) "foo.txt" 9)

现在这给我留下了一个可变字节宽度的文件,其中包含三个 8 位字节和一个 9 位字节宽度。通常我会使用带有位宽输入的 read-file-bytes-to-list 函数来读取具有正确对齐方式的文件,但在这种情况下我真的不能那样做。有没有内置的方法可以更改 Common LISP 中的字节对齐方式?本质上,我想按预期读回整数(65、65、65 和 512)。谢谢你的帮助。我正在使用 SBCL 实现。

编辑:如果使用标准 LISP libraries/utilities 没有方便的方法来处理这个问题,我意识到我很可能必须通过位操作来处理这个问题;没关系。在这样做之前,我只是想知道是否有更好的方法来做到这一点。

首先,当 encoding/decoding 二进制格式时,请考虑使用 lisp-binary

我修改了你的编写函数以修复括号并打印实际的流元素类型:

(defun write-bytes-to-file (bytes file-path bits)
  (with-open-file (stream file-path
                           :direction :output
                           :if-does-not-exist :create
                           :if-exists :append
                           :element-type (list 'unsigned-byte bits))
    (print (stream-element-type stream))
    (dolist (b bytes)
      (write-byte b stream)))))

用这个函数,我构建了一个二进制文件:

USER> (when-let (file (probe-file "/tmp/test.data"))
        (delete-file file))
T
USER> (write-bytes-to-file '(1 1 1 1) "/tmp/test.data" 9)

(UNSIGNED-BYTE 16) 
NIL
USER> (write-bytes-to-file '(1 1 1 1) "/tmp/test.data" 8)

(UNSIGNED-BYTE 8) 
NIL
USER> 

如您所见,编码 (unsigned-byte 9) 元素是通过打开一个使用字节大小为 16 的流来完成的。如果您使用 hexdump 查看生成的文件:

$ hexdump /tmp/test.data
0000000 0001 0001 0001 0001 0101 0101          
000000c

你可以看到前4个是用16位字编码的,后面4个是用8位编码的。为了解码数据,您需要多次打开它(对于写入)并在文件中寻找合适的位置。以下是在 SBCL 上测试过的,不保证它可以移植:

(defun read-file-bytes-to-list (file-path bits count &optional (bits-offset 0))
  (with-open-file (stream file-path :direction :input :element-type (list 'unsigned-byte bits))
    (destructuring-bind (_ bits) (stream-element-type stream)
      (declare (ignore _))
      (loop
         initially (file-position stream (/ bits-offset bits))
         repeat count
         collect (read-byte stream) into bytes
         finally (return (values bytes (* bits (file-position stream))))))))

在函数的末尾,我们 return 读取完成后解码的字节和文件位置,以位表示。返回位数是必要的,因为 file-position return 是流元素大小的倍数。

当我们重新打开文件时,我们将最后一个偏移量作为位,并使用新的流元素类型来计算偏移量以提供给 file-position

例如:

USER> (read-file-bytes-to-list "/tmp/test.data" 9 4)
(1 1 1 1)
64
USER> (read-file-bytes-to-list "/tmp/test.data" 8 4 64)
(1 1 1 1)
96