请分享您的想法,参加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

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

我还在google groups上发起了这个问题的讨论:https://groups.google.com/forum/#!topic/clojure/XzpPPXXhgM4

2 答案

+1

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

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

关键点在这里的java-doc文档中:“如果b的长度为零,则不读取的字节,并返回0”

只有当缓冲区b的长度为零时,才不会读取字节。除非你明确希望当传递缓冲区大小为0时发生无限循环,否则我认为实现不应更改。
javadoc表示,除输入缓冲区长度为0的情况外,不应该返回0。

然而
1. 仍然有一些InputStream实现实际上对JavaDoc返回0。例如,当从一些特定的ZIP文件提取文件时,Apache commons-compress这样做。这可能是一个错误,但这些情况存在。
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的复制是可以的,因为它明确地使用非零大小的缓冲区,因此不会发生这种情况。
0

我认为您误解了文档。这里的关键部分是

此方法会阻塞,直到输入数据可用,......;否则,
至少读取一个字节并存入变量b。

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

目前的代码在读取零时停止是正确的,这只会发生在传入的缓冲区大小为零时。

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

      ...,1024,0,1024, ..., -1
正如我说的:Apache的代码是错误的。
...