common-lisp sbcl

¿Cómo convertir la matriz de bytes a cadena en Common Lisp?



common-lisp sbcl (6)

FLEXI-STREAMS ( http://weitz.de/flexi-streams/ ) tiene una función de conversión portátil

(flexi-streams:octets-to-string #(72 101 108 108 111) :external-format :utf-8) => "Hello"

O, si quieres un stream:

(flexi-streams:make-flexi-stream (flexi-streams:make-in-memory-input-stream #(72 101 108 108 111)) :external-format :utf-8)

devolverá un flujo que lee el texto de byte-vector

Estoy llamando a una API divertida que devuelve una matriz de bytes, pero quiero una secuencia de texto. ¿Hay una manera fácil de obtener un flujo de texto de una matriz de bytes? Por ahora acabo de tirar juntos:

(defun bytearray-to-string (bytes) (let ((str (make-string (length bytes)))) (loop for byte across bytes for i from 0 do (setf (aref str i) (code-char byte))) str))

y luego envuelva el resultado en with-input-from-string, pero esa no puede ser la mejor manera. (Además, es horriblemente ineficiente.)

En este caso, sé que siempre es ASCII, por lo que interpretarlo como ASCII o UTF-8 estaría bien. Estoy utilizando SBCL para Unicode, pero preferiría una solución portátil (incluso ASCII solamente) a una SBCL específica para Unicode.


Hay dos bibliotecas portátiles para esta conversión:

  • Flexi-streams, ya mencionado en otra respuesta.

    Esta biblioteca es más antigua y tiene más funciones, en particular las secuencias extensibles.

  • Babel , una biblioteca específica para la codificación y decodificación de caracteres.

    La principal ventaja de Babel sobre las flexi-streams es la velocidad.

Para obtener el mejor rendimiento, use Babel si tiene las funciones que necesita y, de lo contrario, vuelva a flexi-streams. Debajo de un microbenchmark (ligeramente no científico) que ilustra la diferencia de velocidad.

Para este caso de prueba, Babel es 337 veces más rápido y necesita 200 veces menos memoria.

(asdf:operate ''asdf:load-op :flexi-streams) (asdf:operate ''asdf:load-op :babel) (defun flexi-streams-test (bytes n) (loop repeat n collect (flexi-streams:octets-to-string bytes :external-format :utf-8))) (defun babel-test (bytes n) (loop repeat n collect (babel:octets-to-string bytes :encoding :utf-8))) (defun test (&optional (data #(72 101 108 108 111)) (n 10000)) (let* ((ub8-vector (coerce data ''(simple-array (unsigned-byte 8) (*)))) (result1 (time (flexi-streams-test ub8-vector n))) (result2 (time (babel-test ub8-vector n)))) (assert (equal result1 result2)))) #| CL-USER> (test) Evaluation took: 1.348 seconds of real time 1.328083 seconds of user run time 0.020002 seconds of system run time [Run times include 0.12 seconds GC run time.] 0 calls to %EVAL 0 page faults and 126,402,160 bytes consed. Evaluation took: 0.004 seconds of real time 0.004 seconds of user run time 0.0 seconds of system run time 0 calls to %EVAL 0 page faults and 635,232 bytes consed. |#


Pruebe la función FORMAT . (FORMAT NIL ...) devuelve los resultados como una cadena.


SBCL soporta los llamados Gray Streams . Estas son secuencias extensibles basadas en clases CLOS y funciones genéricas. Podría crear una subclase de secuencia de texto que obtenga los caracteres de la matriz de bytes.


Si no tiene que preocuparse por la codificación UTF-8 (que, básicamente, significa "simplemente ASCII"), puede usar MAP:

(map ''string #'' code-char # (72 101 108 108 111))

Yo digo que vaya con las soluciones propuestas de flexistream o babel.

Pero solo para estar completos y para el beneficio de los futuros googlers que llegan a esta página, quiero mencionar el sb-ext: octets-to-string de sbcl:

SB-EXT:OCTETS-TO-STRING is an external symbol in #<PACKAGE "SB-EXT">. Function: #<FUNCTION SB-EXT:OCTETS-TO-STRING> Its associated name (as in FUNCTION-LAMBDA-EXPRESSION) is SB-EXT:OCTETS-TO-STRING. The function''s arguments are: (VECTOR &KEY (EXTERNAL-FORMAT DEFAULT) (START 0) END) Its defined argument types are: ((VECTOR (UNSIGNED-BYTE 8)) &KEY (:EXTERNAL-FORMAT T) (:START T) (:END T)) Its result type is: *