clojureclojure-contrib

How to convert a sequence to a byte[] in Clojure?


I need to write raw bytes to the file. I do it with:

(.write (FileOutputStream "/path") bytes)

...where bytes must be of type byte[]. Please note it cannot be Byte[].

I tried to convert my sequence with both (bytes) and/or (into-array) functions and got frustrated, one example:

user=> (bytes (into-array (filter #(not (= % 13)) (to-byte-array (File. "e:/vpn.bat")))))
java.lang.ClassCastException: [Ljava.lang.Byte; cannot be cast to [B (NO_SOURCE_FILE:0)

CONTINUED:

The into-array with Byte/TYPE works fine. However, the byte-array does not. The file gets empty:

(import 'FileOutputStream)
(use 'clojure.contrib.io)

(defn remove-cr-from-file [file]
  (with-open [out (FileOutputStream. file)]
    (let [dirty-bytes (to-byte-array file)
          clean-seq   (filter #(not (= 13 %)) dirty-bytes)
          clean-bytes (byte-array clean-seq)]
      (.write out clean-bytes))))

Solution

  • Update: the new part of the question ("CONTINUED") is answered towards the end.


    Just to make it clear what actually happens here:

    This question actually illustrates an interesting point: the array cast functions -- bytes, ints, ... -- are not and cannot be used as conversion functions. They only cast to the target type, meaning in particular that the input to bytes must already be an array of appropriate type.

    This makes sense, since converting from int[] to long[] is not a simple matter of viewing the numbers in a different light -- you'd also have to allocate a different amount of storage for the array -- so being able to tell just by looking at the operator whether the operation in question is a conversion or a cast is a Good Thing.

    The reason why casts are useful in a dynamic language such as Clojure has to do with efficiency (you can use casting alongside type hints to speed things up) and interop (where you often need a thing of just the right type). The reason why the compiler can't just infer the correct array type is because there is not always enough information to do so (not to mention it might not even be clear what the "correct" type might be).

    To fix the snippet in question, one could use either Byte/TYPE (as suggested by Jieren) or skip the into-array and bytes and wrap the filter in bytes-array instead (as suggested by Brenton Ashworth).


    The problem with the code newly included in the question text is that it opens a FileOutputStream on the file prior to reading its contents. The act of opening the FOS already clears the file:

    (with-open [out (FileOutputStream. some-file)]
      :foo)
    
    ; => :foo
    ; side effect: some-file is now empty
    

    You'll have to read from the file outside the with-open:

    (let [foo (byte-array
               (filter #(not= 13 %)
                       (to-byte-array some-file)))]
      (with-open [out (FileOutputStream. some-file)]
        (.write out foo)))