在 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
假设我有一些实用函数定义为:
(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