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

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

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

2 答案

+1

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

添加了一个带有补丁的链接。谢谢...
我认为这并不是一个bug。

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

只有当缓冲区b的长度为零时,才不会读取任何字节。除非您明确希望传递一个长度为0的缓冲区时产生一个无限循环,否则我认为实现不应改变。
Javadoc中表示,除非输入缓冲区的长度为0,否则不应返回0。

但是
1. 有一些InputStream实现确实按照JavaDoc返回0。例如,当从某些特定的ZIP文件中提取文件时,Apache commons-compress就是这样做的。这可能是bug,但这些事物确实存在。
2. 其他InputStream->OutputStream拷贝实现会一直读取流,直到接收到`-1`。例如
     -  链接
     - 链接
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库中存在的问题。
...