【问题标题】:Memory-friendly way of writing an InputStream to a File将 InputStream 写入文件的内存友好方式
【发布时间】:2020-03-12 14:09:31
【问题描述】:

我正在尝试为图像编写一个批量下载器。从URLConnection 获取 InputStream 很容易,但下载所有文件需要一段时间。使用多线程确实可以加快速度,但是有很多线程下载文件可能会占用大量内存。这是我发现的:

in 成为InputStreamfile 目标Filefos 一个FileOutputStreamfile

简单的方法

fos.write(in.readAllBytes());

读取整个文件,写入返回的byte[]。可能可用于获取网站源,对于较大的文件(例如图像)没有好处。

编写块

 byte[] buffer = new byte[bufsize];
 int read;
 while ((read = in.read(buffer, 0, bufsize)) >= 0) {
     fos.write(buffer, 0, read);
 }

对我来说似乎更好。

in.transferTo(fos)

in.transferTo(fos);

在内部写入块,如上所示。

文件.copy()

Files.copy(in, file.toPath(),  StandardCopyOption.REPLACE_EXISTING);

似乎使用本机实现。

当并行执行数十次时,我应该使用其中哪一个来最小化内存使用量?

这是一个有趣的小项目,外部库对于那个 IMO 来说太过分了。我也不能使用ImageIO,因为它不能处理 webms、一些 pngs/jpgs 和动画 gif。

编辑:
这个问题是基于并发写入是可能的假设。但是,情况似乎并非如此。我可能会同时获取图像链接,然后一个接一个地下载它们。无论如何感谢您的回答!

【问题讨论】:

  • 当并行执行数十次时,我应该使用其中哪一个来最小化内存使用量? 没有。不要并行复制数十次。这将需要比串行执行复制更多的内存,并且由于写入操作阻塞而需要更多的时间。
  • 我的措辞不好。它使用 InputStream 将数据从在线图像复制到文件中。此外,每张图片都会被下载到一个单独的文件中,因此阻止应该不是问题。
  • 这些单独的文件是否也在单独驱动器上的单独文件系统上?那么,同时写入文件 1 和文件 2 不会阻塞?很抱歉,这不正确。
  • 好吧,我不知道

标签: java file memory download


【解决方案1】:

简短的回答是:从内存使用的角度来看,最好的解决方案是使用分块读取和存储数据的版本。

缓冲区大小的选择基本上要考虑同时下载的数量、可用内存、下载速度和目标驱动器在数据传输率和IOPS方面的效率。

长答案是文件的并发下载并不一定意味着下载会更快。 实际加快整体下载时间的同时下载次数主要取决于:

  • 从中下载的主机数量
  • 您所在主机的 Internet 连接速度 下载,受本主机网卡速度限制
  • 您的互联网连接速度,受此主机的网络适配器速度限制
  • 您从中下载的主机的存储 IOps
  • 您下载到的存储空间的 IOps
  • 您从中下载的主机上的存储传输速率
  • 您下载到的存储的传输速率
  • 本地和远程主机的性能。例如,一些较旧或低成本的 Android 设备可能会受到 CPU 速度的限制。

例如,如果源主机有单个硬盘驱动器并且单个连接已经提供了完整的连接速度,那么使用多个连接是没有用的,因为它会通过产生切换 beetwen 传输的开销而使下载变慢文件。

也可能是源主机对单个连接有速度限制,因此多个连接可以加快速度。

HDD 驱动器的 IOPS 值通常在 80 IOPS 左右,传输速率在 80 MB/s 左右,这些因素可能会限制下载/上传速度。所以实际上你不能从这样的磁盘写入或读取超过每秒 80 个文件,并且超过大约 80MB/s 的传输限制,当然这几乎不取决于磁盘型号。

SSD 驱动器通常具有数万 IOPS 和 > 400 MB/s 的传输速率,因此限制要大得多,但对于真正快速的互联网连接来说,它们仍然很重要。

【讨论】:

    【解决方案2】:

    我在互联网上找到了基于时间的比较(因此是性能),这里是 journaldev.com/861/java-copy-file

    但是,如果您专注于内存,您可以尝试使用类似于 @pasha701 here 提出的代码之类的方法自己测量内存消耗

    Runtime runtime = Runtime.getRuntime();
    long usedMemoryBefore = runtime.totalMemory() - runtime.freeMemory();
    System.out.println("Used Memory before" + usedMemoryBefore);
    // copy file method here
    long usedMemoryAfter = runtime.totalMemory() - runtime.freeMemory();
    System.out.println("Memory increased:" + (usedMemoryAfter-usedMemoryBefore));
    

    请注意,此返回值以字节为单位,除以 1000000 得到以 MB 为单位的值。

    【讨论】:

      猜你喜欢
      • 2017-10-12
      • 2016-12-22
      • 2014-03-24
      • 1970-01-01
      • 2016-03-19
      • 2016-07-20
      • 1970-01-01
      • 2014-05-05
      • 2015-02-24
      相关资源
      最近更新 更多