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)) 吗?

我也在该问题中发布了至 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 的实现实际上返回 0,违反了 JavaDoc。例如,Apache commons-compress 在从某些特定的 ZIP 文件中提取文件时执行此操作。这可能是一个错误,但这些东西确实存在。
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
3. clojure.java.io/copy 函数需要对 `0` 进行某种处理,这是一个灰色地带,两种方式在规范层面并没有优劣之分。
by
请参阅我的回答。文档中非常明确地指出:a) 方法会阻塞,直到有输入数据可用(或EOF或异常发生);b) 至少会读取一个字节并存储到b中(除非EOF,异常或缓冲区大小为0)。

Apache的copyLarge()函数对大小为0的缓冲区无法正常工作。Jetty的copy却是好的,因为它显式使用了一个非零大小的缓冲区,所以这种情况永远不会发生。
0
by

我认为你是误解了文档。以下关键部分很重要:

该方法将阻塞,直到有输入数据可用,... ...;否则,
至少会读出一个字节并存储到b中。

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

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

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

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