无法将多个流合并为一个

时间:2018-12-28 14:42:38

标签: scala

我正在尝试将多个文件的内容合并为一个文件并测试我的代码,

我的测试目录中有3个文件- file1.txt:这个 file2.txt:是 file3.txt:测试

,这里是将所有3个文件合并为流并写入单个“ out.txt”文件的代码。 但是这段代码只将一个文件的内容写入“ out.txt”,为什么?

**

import java.io.{File, FileInputStream, InputStream, SequenceInputStream}
  import scala.collection.JavaConverters.asJavaEnumeration
  def mergeInputStreams(files: Iterator[File]): InputStream = new SequenceInputStream(asJavaEnumeration(files.map(new FileInputStream(_))))
  val d = "/Users/pink/temp"
  val file = new File(d);
  //file.listFiles.iterator.foreach(println)
  val fss = mergeInputStreams(file.listFiles.toIterator)
  val outfile = new File("/Users/pink/tmp/out.txt")
  val os = new FileOutputStream(outfile)
  try {
    while (fss.available()>0) {
      os.write(fss.read())
    }
  } finally {
    fss.close()
    os.close()
  }

**

我希望以上代码应生成一个具有以下内容的文件- out.txt: 这是测试

2 个答案:

答案 0 :(得分:1)

发生这种现象是因为fss.available() > 0是对此类任务的错误检查。 InputStream.available()的JavaDoc说(强调是我的)

  

通过下一次为此输入方法调用,返回对该输入流 无阻塞 可以读取(或跳过)的字节数的估计。流。

fss.available() > 0保证流没有结束但在相反的方向上是不正确的事实:fss.available()可能是0,但您仍然可以读取更多数据。即使对于基于文件的InputStream,这也可能是正确的。例如,假设文件实际上位于使用某些网络文件系统安装的其他服务器上。在这种情况下,返回客户端缓存的字节数的available实现是接口的合理实现,因为获取更多数据确实需要通过网络进行阻塞请求。

如果您阅读了SequenceInputStream.available()的JavaDoc,您可能会看到

  

...
  此方法仅调用当前基础输入流的available并返回结果。

这可能是接口协定的唯一明智的实现:通常,您无法以非阻塞方式区分is.available() == 0(由于到达末尾)和更多等待或阻塞操作的情况需要获取更多数据。

available方法的意图将用于各种优化,而不是检查流的末尾。对于InputStream是否到达终点的唯一正确检查可能是阻塞read() == -1

答案 1 :(得分:0)

mergeInputStreams正常工作,但是将InputStream写入FileOutputStream的while循环无法获取所有内容。 @SergGr的答案非常清楚地说明了为什么会这样。通过用IOUtils复制方法替换while循环读写,所有内容都会以错误的顺序写入文件。输出的顺序由以下代码行确定:

file.listFiles.toIterator

对输入文件进行排序后,将其输入out.txt:

this
is
test

这是执行此操作的代码:

import scala.collection.JavaConverters.asJavaEnumeration
import java.io.{File, FileInputStream, InputStream, FileOutputStream, SequenceInputStream, IOException}
import org.apache.commons.io.IOUtils

object Example1 {
  def main(args: Array[String]): Unit = {
    def mergeInputStreams(files: Iterator[File]): InputStream = 
        new SequenceInputStream(asJavaEnumeration(files.map(new FileInputStream(_))))
    val d = "/Users/pink/temp"
    val file = new File(d)
    val fss = mergeInputStreams(file.listFiles.toList.sorted.toIterator)
    val os = new FileOutputStream(new File("/Users/pink/tmp/out.txt"))
    try {
      IOUtils.copy(fss, os)
    }
    catch {
      case e: IOException => println(s"IO exception: $e.getMessage")
    }
  }
}

此解决方案肯定有效,但是我们使用了许多Java代码,这些副作用会在我们的类型系统未知的情况下发生。因此,我们必须小心捕捉异常并将IO操作准确地放置在我们想要的位置。

使用fs2 Streams是更实用的写方法。这是经过readme file修改以匹配您的代码正在执行的操作的示例:

import cats.effect.{ExitCode, IO, IOApp, Resource}
import cats.implicits._
import fs2.{io, text, Stream}
import java.nio.file.Paths
import java.util.concurrent.Executors
import scala.concurrent.ExecutionContext

object Example2 extends IOApp {
  private val blockingExecutionContext =
        Resource.make(IO(ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(2))))(ec => IO(ec.shutdown()))

  val converter: Stream[IO, Unit] = Stream.resource(blockingExecutionContext).flatMap { blockingEC =>

    val f1 = io.file.readAll[IO](Paths.get("/Users/pink/temp/file1.txt"), blockingEC, 4096)
    val f2 = io.file.readAll[IO](Paths.get("/Users/pink/temp/file2.txt"), blockingEC, 4096)
    val f3 = io.file.readAll[IO](Paths.get("/Users/pink/temp/file3.txt"), blockingEC, 4096)

    (f2 merge f3 merge f1)
      .through(text.utf8Decode)
      .through(text.lines)
      .intersperse("\n")
      .through(text.utf8Encode)
      .through(io.file.writeAll(Paths.get("/Users/pink/tmp/out.txt"), blockingEC))
  }

  def run(args: List[String]): IO[ExitCode] =
    converter.compile.drain.as(ExitCode.Success)
}

在调用run之前,不会发生任何IO操作,并且所有异常都将保留在IO类型内,因此我们不必担心捕获整个代码。