请在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[])

Java 文档中指出以下内容

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

这意味着返回值 -1 表示流结束,而返回值 0 并不表示流结束。然而,上述代码位置 2 的条件,当 .read 返回的值小于 1 时,停止递归。

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

1024, 0, 1024, 201, -1

当整个流有 2249 字节时,clojure.java/io 仅拷贝前 1024 字节。这是预期的行为吗?应将位置 1 的条件更改为 (not (neg? size)) 吗?

我将此问题也发布到了 google groups: https://groups.google.com/forum/#!topic/clojure/XzpPPXXhgM4

2 个回答

+1
by

我同意这确实是一个bug,并将为此创建一个Jira问题。

by
by
我认为这并不是一个bug。

关键点在于这部分Java文档:"如果b的长度为零,则不读取任何字节,并返回0"。

只有当缓冲区b的长度为零时,才不读取任何字节。除非你明确想要如果传递0大小的缓冲区时出现无限循环,否则我的观点是,在实现中不应进行更改。
by
Javadoc表示在输入缓冲区长度为0时不应返回0。

然而
1.有些InputStream实现确实会按照JavaDoc返回0。例如,当从某些特定的ZIP文件中提取文件时,Apache commons-compress会这样做。这可能是一个bug,但这类事情确实存在。
2.其他InputStream->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
clojure.java.io/copy必须以某种方式处理`0`,这是一个模糊区域,两种方式在规范意义上都没有更好的。
查看我的回答。文档非常明确,a)该方法会在输入数据可用(或者EOF或异常)时阻塞,b)至少读取一个字节并将其存储到b中(除非EOF、异常或缓冲区大小为零)。

Apache的copyLarge()函数在大小为零的缓冲区中不会正常工作。Jetty的copy是好的,因为它明确使用非零大小的缓冲区,所以这种条件绝不会发生。
0

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

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

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

当前代码正确地停止在零,这将发生在(仅发生于)传入的缓冲区大小为零时。

我在使用1024大小缓冲区运行代码时观察到读取返回值的这一序列。因此,clojure的copy函数在零处停止复制,IOUtils ja Jetty复制了整个流。InputStream来自apache commons-compress。因此,这里的InputStream没有按照JavaDoc正常运行。

      ...,1024,0,1024, ..., -1
正如我所说:Apache的代码碎了。
by
...