2024 Clojure 状态调查!中分享您的想法。

欢迎!请参阅关于页面,了解更多关于这个网站如何运作的信息。

0
IO

我在 clojure.java.io/copy 函数的实现中发现了奇怪的行为。

https://github.com/clojure/clojure/blob/ee1b606ad066ac8df2efd4a6b8d0d365c206f5bf/src/clj/clojure/java/io.clj#L391

(defn copy
  "Copies input to output.  Returns nil or throws IOException.
  Input may be an InputStream, Reader, File, byte[], char[], or String.
  Output may be an OutputStream, Writer, or File.
  Options are key/value pairs and may be one of
    :buffer-size  buffer size to use, default is 1024.
    :encoding     encoding to use if converting between
                  byte and char streams.   
  Does not close any streams except those it opens itself 
  (on a File)."
  {:added "1.2"}
  [input output & opts]
  (do-copy input output (when opts (apply hash-map opts))))

实际的复制操作是从 InputStream 复制到 OutputStream 时在这里实现的。

https://github.com/clojure/clojure/blob/ee1b606ad066ac8df2efd4a6b8d0d365c206f5bf/src/clj/clojure/java/io.clj#L306

(defmethod do-copy [InputStream OutputStream] [^InputStream input ^OutputStream output opts]
  (let [buffer (make-array Byte/TYPE (buffer-size opts))]
    (loop []
      (let [size (.read input buffer)]  ;;; XXX point 1
        (when (pos? size)               ;;; XXX point 2
          (do (.write output buffer 0 size)
              (recur)))))))

点1处的 .read 函数是https://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html#read(byte[])

javadoc 说明如下:

从输入流中读取一些字节并将它们存储到缓冲数组 b 中。实际读取的字节数作为整数返回。此方法阻塞,直到有输入数据可用、检测到文件末尾或抛出异常。
如果 b 的长度为零,则不读取任何字节,并返回 0;否则,将尝试读取至少一个字节。如果没有可用的字节,因为流位于文件末尾,则返回值 -1;否则,至少读取一个字节并将其存储在 b 中。

这意味着返回值 -1 表示流末尾,而返回值 0 并不表示流末尾。但是,如上代码中的点2条件,当 .read 返回小于 1 的值时停止递归。

现在考虑一个情况下,.read 在连续调用中返回这个值序列

1024, 0, 1024, 201, -1

clojure.java/io 仅复制前 1024 字节,当整个流有 2249 字节时。这是预期的行为吗?点1的条件应该是 (not (neg? size)) 吗?

我也在 google 组发布了这个问题:https://groups.google.com/forum/#!topic/clojure/XzpPPXXhgM4

2 答案

+1

我同意这是一个错误,并将为其创建一个Jira。

已通过补丁添加https://clojure.atlassian.net/browse/CLJ-2533。谢谢...
我不认为这是一个错误。

关键点在于java-doc中的这一部分:"如果b的长度为零,则不读取任何字节,并返回0 "

只有当buffer b的长度为零时,才不读取字节。我认为,除非您明确想要在传递0大小的缓冲区时产生无限循环,否则实现不应被更改。
Javadoc说明除输入缓冲区长度为0外,不应该返回0。

然而
1. 有一些InputStream实现实际上返回0,针对JavaDoc。例如,Apache commons-compress在从特定的ZIP文件中提取文件时会这样做。这可能是错误,但确实存在 这样的情况。
2. 其他InputStream-to-OutputStream复制实现会读取流直到收到“-1”。例如
     -  https://github.com/apache/commons-io/blob/master/src/main/java/org/apache/commons/io/IOUtils.java#L1083
     - https://github.com/eclipse/jetty.project/blob/9706d70484863a014d3604e5e7cb4af40aa4cb1e/jetty-util/src/main/java/org/eclipse/jetty/util/IO.java#L161
3. clojure.java.io/copy 操作必须某种方式处理 `0`,这是一个模糊区域,两种方式在规范中都不优于其他。
by
请见我的回答。文档非常清晰地说明了以下几点:a) 方法会阻塞直到输入数据可用(或 EOF 或异常),b)至少读取一个字节并存储到 b 中(除非 EOF、异常或缓冲区大小为零)。

Apache 的 copyLarge() 函数在缓冲区大小为零时无法正确工作。Jetty 的 copy 是好的,因为它明确使用了一个非零大小的缓冲区,这样就不会发生这种情况。
0
by

我认为你误解了文档。关键部分如下

此方法会在数据可用时阻塞,... ...;否则,
至少会读取一个字节并将它存储到 b 中。

因此,它不会读取零字节,除非缓冲区大小为零。

当前代码在零处停止是正确的——这只会发生在传入的缓冲区大小为零的情况下。

by
我在运行代码并使用 1024 字节缓冲区大小时观察到了以下读取返回值序列。所以在这里 clojure 的 copy 函数在零处停止复制,IOUtils ja Jetty 则复制了整个流。InputStream 来自 apache commons-compress。所以在这里 InputStream 不按 JavaDoc 中的说明工作。

       ...,1024,0,1024, ..., -1
by
正如我所说:Apache 的代码是错误的。
by
https://issues.apache.org/jira/browse/COMPRESS-491 这里是.apache库中存在的问题。
...