在Scala中,我如何将文件读取到InputStream,然后将其写入OutputStream?

23

我想用Scala中基本的Java代码从文件中读取并写入OutputStream,但是当我在Scala中使用通常的while( != -1 )时,会出现警告"comparing types of Unit and Int with != will always yield true".

以下是代码:

    val file = this.cache.get(imageFileEntry).getValue().asInstanceOf[File]
    response.setContentType( "image/%s".format( imageDescription.getFormat() ) )

    val input = new BufferedInputStream( new FileInputStream( file ) )
    val output = response.getOutputStream()

    var read : Int = -1

    while ( ( read = input.read ) != -1 ) {
        output.write( read )
    }

    input.close()
    output.flush()

我应该如何在Scala中从输入流写入输出流?

我主要感兴趣的是一种类似于Scala的解决方案。


从性能角度来看,使用中间缓冲区而不是逐字节读写可能是一个好主意。 - Knut Arne Vedaa
这就是为什么有一个BufferedInputStream的原因。 - Maurício Linhares
5个回答

45
你可以这样做:
Iterator 
.continually (input.read)
.takeWhile (-1 !=)
.foreach (output.write)

2
好的,这看起来像是一个真正的Scala解决方案,但是这个continually调用会将文件加载到内存中还是在foreach循环运行时调用此函数?另外,您能详细说明一下takeWhile方法吗?为什么您不必使用_参数或自己定义参数? - Maurício Linhares
1
@Maurício 这是一个迭代器,所以所有操作都是按需完成的。在 foreach 之前,什么也不会发生 - 您只会获得新的 Iterator 对象,在 nexthasNext 之前进行一些预处理。在 foreach 上,对于每个 input.read 执行一个 output.write,然后它的值会立即被遗忘并进行垃圾回收。 - Daniel C. Sobral
使用scala-io孵化器项目的版本会很不错。 - gerferra
1
值得注意的是,为了获得最大的性能,输入和输出流都应该被缓冲(使用Buffered{Input,Output}Stream)。但即使如此,它仍然比非Scala方式慢得多。 - lyomi

19

如果这很慢:

Iterator 
.continually (input.read)
.takeWhile (-1 !=)
.foreach (output.write)

您可以展开它:

val bytes = new Array[Byte](1024) //1024 bytes - Buffer size
Iterator
.continually (input.read(bytes))
.takeWhile (-1 !=)
.foreach (read=>output.write(bytes,0,read))
output.close()

假设您只需要读取1024字节,这是可以的。但如果我不知道需要读取多少字节,直到达到分隔符之类的东西呢? - foxtrotuniform6969

7
Scala中,赋值语句永远返回Unit类型。因此,read=input.read返回的是Unit类型,它永远不可能等于-1。你可以这样做:
while ({read = input.read; read != -1}) {
  output.write(read)
}

1
你不能在 while/if/for 从句中有多个语句。这段代码会产生编译错误。但感谢分配任务,我以为它会像 Java 那样运行。 - Maurício Linhares
嗨,Mauricio!是的,{} 是他例子中的关键点。我们称它们为块。里面的所有内容都将被执行,并最终返回具有最后一个表达式类型的结果。 - AndreasScheinert
抱歉,我稍微晚了一点加上大括号;) - Kim Stebel

5
def stream(inputStream: InputStream, outputStream: OutputStream) =
{
  val buffer = new Array[Byte](16384)

  def doStream(total: Int = 0): Int = {
    val n = inputStream.read(buffer)
    if (n == -1)
      total
    else {
      outputStream.write(buffer, 0, n)
      doStream(total + n)
    }
  }

  doStream()
}

2
这是一个更好的解决方案。上述解决方案非常慢。每读取一个字节就要进行一次调用开销?真的吗?在批量数据传输中,这怎么能被接受呢? - Christopher

0

我们可以使用类型类以通用和类型安全的方式将输入流复制到输出流。类型类是一个概念。这是一种多态的方法。特别地,它是参数多态,因为多态行为是使用参数编码的。在我们的情况下,我们的参数将是Scala traits的通用类型。

让我们创建Reader[I]Writer[O] traits,其中IO分别是输入和输出流类型。

trait Reader[I] {
  def read(input: I, buffer: Array[Byte]): Int
}

trait Writer[O] {
  def write(output: O, buffer: Array[Byte], startAt: Int, nBytesToWrite: Int): Unit
}

我们现在可以编写一个通用的复制方法,它可以操作订阅这些接口的对象。
object CopyStreams {

  type Bytes = Int

  def apply[I, O](input: I, output: O, chunkSize: Bytes = 1024)(implicit r: Reader[I], w: Writer[O]): Unit = {
    val buffer = Array.ofDim[Byte](chunkSize)
    var count = -1

    while ({count = r.read(input, buffer); count > 0})
      w.write(output, buffer, 0, count)
  }
}

请注意这里的隐式rw参数。本质上,我们在说只有当作用域中存在Reader[I]Writer[O]值时,CopyStreams[I,O].apply才能正常工作。这将使我们能够无缝地调用CopyStreams(input, output)。

然而,需要注意的是,此实现是通用的。它操作独立于实际流实现的类型。

在我的特定用例中,我需要将S3对象复制到本地文件。因此,我创建了以下隐式值。

object Reader {

  implicit val s3ObjectISReader = new Reader[S3ObjectInputStream] {
    @inline override def read(input: S3ObjectInputStream, buffer: Array[Byte]): Int =
      input.read(buffer)
  }
}


object Writer {

  implicit val fileOSWriter = new Writer[FileOutputStream] {
    @inline override def write(output: FileOutputStream,
                               buffer: Array[Byte],
                               startAt: Int,
                               nBytesToWrite: Int): Unit =
      output.write(buffer, startAt, nBytesToWrite)
  }
}

现在我可以做到以下几点:

val input:S3ObjectStream = ...
val output = new FileOutputStream(new File(...))
import Reader._
import Writer._
CopyStreams(input, output)
// close and such...

如果我们需要复制不同类型的流,只需编写一个新的ReaderWriter隐式值即可。我们可以在不改变CopyStreams代码的情况下使用它!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接