解析一个小端二进制文件,填充到矩阵中

时间:2012-10-26 03:41:01

标签: clojure

我有一个包含X×X矩阵的二进制文件。文件本身是一系列单精度浮点数(little-endian)。我想做的是解析它,并将其填入一些合理的clojure矩阵数据类型。

感谢this question,我发现我可以解析带有光泽的二进制文件。我现在的代码看起来像这样:

(ns foo.core
  (:require gloss.core)
  (:require gloss.io)
  (:use [clojure.java.io])
  (:use [clojure.math.numeric-tower]))

(gloss.core/defcodec mycodec
  (gloss.core/repeated :float32 :prefix :none))

(def buffer (byte-array (* 1200 1200)))

(.read (input-stream "/path/to/binaryfile") buffer)

(gloss.io/decode mycodec buffer)

这需要一段时间才能运行,但最终会丢弃一大堆数字。不幸的是,这些数字都是错的。经过进一步调查,这些数字被视为大端。

假设有一些方法可以将这些二进制文件作为little-endian读取,我想将结果填充到矩阵中。 This question似乎已经决定使用Incanter及其Parallel Colt表示,然而,这个问题来自于'09,我希望坚持使用clojure 1.4和lein 2.在我狂热的谷歌搜索的某个地方,我看到了其他建议使用jblas或mahout。这些天是否有一个“最佳”的clojure矩阵库?

编辑:读取二进制文件非常接近。感谢这个方便的nio wrapper,我能够将内存映射的字节缓冲区作为一个简短的单行,甚至重新排序:

(ns foo.core
  (:require [clojure.java.io :as io])
  (:require [nio.core :as nio])
  (:import [java.nio ByteOrder]))

(def buffer (nio/mmap "/path/to/binaryfile"))

(class buffer) ;; java.nio.DirectByteBuffer

(.order buffer java.nio.ByteOrder/LITTLE_ENDIAN)
;; #<DirectByteBuffer java.nio.DirectByteBuffer[pos=0 lim=5760000 cap=5760000]>

但是,在不执行中间(def)步骤的情况下重新排序会失败:

(.order (nio/mmap f) java.nio.ByteOrder/LITTLE_ENDIAN)

;; clojure.lang.Compiler$CompilerException: java.lang.IllegalArgumentException: Unable to resolve classname: MappedByteBuffer, compiling:(/Users/peter/Developer/foo/src/foo/core.clj:12)
;;  at clojure.lang.Compiler.analyzeSeq (Compiler.java:6462)
;;     clojure.lang.Compiler.analyze (Compiler.java:6262)
;; etc...

我希望能够在函数内部创建重新排序的字节缓冲区而不定义全局变量,但是现在它似乎不喜欢这样。

此外,一旦我重新排序,我不完全确定如何处理我的DirectByteBuffer,因为它似乎不可迭代。也许对于读取此缓冲区对象(进入JBLAS矩阵)的剩余步骤,我将创建第二个问题。

编辑2:我将下面的答案标记为已接受,因为我认为我原来的问题结合了太多事情。一旦我弄清楚其余的内容,我将尝试使用以此ByteBuffer开头的完整代码更新此问题,并将其读入JBLAS矩阵(这似乎是正确的数据结构)。

如果有人感兴趣,我可以创建一个函数,返回一个正确排序的bytebuffer,如下所示:

;; This works!
(defn readf [^String file]
  (.order
   (.map
    (.getChannel
     (java.io.RandomAccessFile. file "r"))
    java.nio.channels.FileChannel$MapMode/READ_ONLY 0 (* 1200 1200))
   java.nio.ByteOrder/LITTLE_ENDIAN))

我找到的nio包装器看起来非常简化/美化,但看起来我要么没有正确使用它,要么出现了问题。用nio包装器回顾我的发现:

;; this works
(def buffer (nio/mmap "/bin/file"))
(def buffer (.order buffer java.nio.ByteOrder/LITTLE_ENDIAN))
(def buffer (.asFloatBuffer buffer))

;; this fails
(def buffer
  (.asFloatBuffer
   (.order
    (nio/mmap "/bin/file")
    java.nio.ByteOrder/LITTLE_ENDIAN)))

可悲的是,这是另一天的谜团,或者是另一个StackOverflow问题。

1 个答案:

答案 0 :(得分:2)

打开FileChannel(),然后获取内存映射缓冲区。网络上有很多关于此步骤的教程。

通过调用order(endian-ness)(而不是order的无参数版本)将缓冲区的顺序切换为小端。最后,提取浮点数的最简单方法是在其上调用asFloatBuffer()并使用生成的缓冲区来读取浮点数。

之后,您可以将数据放入您需要的任何结构中。

编辑以下是如何使用API​​的示例。

;; first, I created a 96 byte file, then I started the repl
;; put some little endian floats in the file and close it
user=> (def file (java.io.RandomAccessFile. "foo.floats", "rw"))
#'user/file
user=> (def channel (.getChannel file))
#'user/channel
user=> (def buffer (.map channel java.nio.channels.FileChannel$MapMode/READ_WRITE 0 96))
#'user/buffer
user=> (.order buffer java.nio.ByteOrder/LITTLE_ENDIAN)
#<DirectByteBuffer java.nio.DirectByteBuffer[pos=0 lim=96 cap=96]>
user=> (def fbuffer (.asFloatBuffer buffer))
#'user/fbuffer
user=> (.put fbuffer 0 0.0)
#<DirectFloatBufferU java.nio.DirectFloatBufferU[pos=0 lim=24 cap=24]>
user=> (.put fbuffer 1 1.0)
#<DirectFloatBufferU java.nio.DirectFloatBufferU[pos=0 lim=24 cap=24]>
user=> (.put fbuffer 2 2.3)
#<DirectFloatBufferU java.nio.DirectFloatBufferU[pos=0 lim=24 cap=24]>
user=> (.close channel)
nil

;; memory map the file, try reading the floats w/o changing the endianness of the buffer
user=> (def file2 (java.io.RandomAccessFile. "foo.floats" "r"))
#'user/file2
user=> (def channel2 (.getChannel file2))                                                
#'user/channel2
user=> (def buffer2 (.map channel2 java.nio.channels.FileChannel$MapMode/READ_ONLY 0 96))
#'user/buffer2
user=> (def fbuffer2 (.asFloatBuffer buffer2))
#'user/fbuffer2
user=> (.get fbuffer2 0)
0.0
user=> (.get fbuffer2 1)
4.6006E-41
user=> (.get fbuffer2 2)
4.1694193E-8

;; change the order of the buffer and read the floats    
user=> (.order buffer2 java.nio.ByteOrder/LITTLE_ENDIAN)                                 
#<DirectByteBufferR java.nio.DirectByteBufferR[pos=0 lim=96 cap=96]>
user=> (def fbuffer2 (.asFloatBuffer buffer2))
#'user/fbuffer2
user=> (.get fbuffer2 0)
0.0
user=> (.get fbuffer2 1)
1.0
user=> (.get fbuffer2 2)
2.3
user=> (.close channel2)
nil
user=>