确定一个InputStream的大小

109

我的当前情况是:我需要读取一个文件并将其内容放入InputStream中。然后,我需要将InputStream的内容放入一个字节数组中,这需要(据我所知)InputStream的大小。有什么想法吗?

按照要求,我将展示从上传文件创建的输入流

InputStream uploadedStream = null;
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
java.util.List items = upload.parseRequest(request);      
java.util.Iterator iter = items.iterator();

while (iter.hasNext()) {
    FileItem item = (FileItem) iter.next();
    if (!item.isFormField()) {
        uploadedStream = item.getInputStream();
        //CHANGE uploadedStreambyte = item.get()
    }
}

该请求是一个HttpServletRequest对象,就像FileItemFactoryServletFileUpload一样,都来自于Apache Commons FileUpload包。

13个回答

89

这是一个非常古老的帖子,但当我谷歌这个问题时,它仍然是第一个弹出来的东西。所以我想要补充一下:

InputStream inputStream = conn.getInputStream();
int length = inputStream.available();

对我有用。比这里其他答案要简单得多。

警告:此解决方案无法可靠地提供有关流总大小的结果。除了JavaDoc中所述之外:

请注意,虽然一些{@code InputStream}实现将返回流中的总字节数,但许多实现不会这样做。


99
我认为那不准确。根据Javadocs所述:“请注意,尽管InputStream的一些实现将返回流中的总字节数,但许多实现不会。使用此方法的返回值来分配旨在容纳此流中所有数据的缓冲区是错误的。”所以它可能在您的虚拟机上运行良好,但在其他人的虚拟机上可能无法正常工作。http://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html#available() - Marvo
3
哦,心理作用。很棒的发现!我想这个技巧对需要具有可移植性的代码来说并不好用。我在学校项目中使用它,所以对我而言是有效的。非常感谢,了解这点对我以后很有帮助! - W. B. Reed
4
当您像在ByteArrayInputStream中一样将所有数据存储在内存中时,这是完美的。 - stacker
12
这是错误的!根据文档,关于available()方法说:“请注意,虽然InputStream的某些实现将返回流中的总字节数,但许多实现不会这样做。使用此方法的返回值来分配旨在容纳该流中所有数据的缓冲区是不正确的。” - GVillani82
4
答案含糊不清。那么 Android 文档 呢?明确说明了:"请注意,该方法提供的保证非常弱,因此 在实践中并不是非常有用"。 - Ksenia
显示剩余8条评论

44
我会使用 ByteArrayOutputStream 读取数据,之后使用 toByteArray() 方法将其转换为字节数组。你不需要提前定义数组的大小(虽然如果你知道大小,这可能是一种优化方案)。在许多情况下,你并不知道大小。请注意保留原文中的html标签。

在字节/字符转换方面需要小心。这最初是一个字节流吗?如果它包含字符,那么它们是如何编码的等等。如果您通过网络连接获取这些数据,我会怀疑这是主要的性能瓶颈,并且我不会担心转换开销。 - Brian Agnew
我目前将图像的InputStream作为BinaryStream读入数据库,这似乎效果很好,因为我可以在之后读取文件并且仍然是一张图片。 - ChronoXIII
好的,如果String.getBytes确实存在类型转换问题,那么对我的代码进行以下更改(在初始帖子中显示为//CHANGE)是否有效? - ChronoXIII
2
是的。您可以获得完整的原始字节数组,无需转换,也没有任何问题。 - akarnokd
3
如果你只需要尺寸,这么做不是非常浪费内存吗? - cdmckay
显示剩余4条评论

24

在我的情况下,inputstream 包含来自 HTML 表单的上传文件,我无法获取文件大小,因为我没有从硬盘加载文件。 - ChronoXIII
1
如果您的输入流支持mark(),您可以在开始处进行标记,然后完全读取它 - 然后重置()并开始处理它。 - akarnokd
1
文件是一张图片(可能很大),所以通过流两次读取会导致性能问题,不是吗? - ChronoXIII
尝试一下。如果它非常缓慢,则存在性能问题。否则,就没有问题。就是这么简单。 - Bombe
@ChronoXIII:没错。这就是为什么我要求提供一小段代码示例,以便我们能够看到您的情况。如果您的图像已经在内存中(感谢文件上传或其他方式),那么它会打开更多选项。 - akarnokd

17

我只想补充一点,Apache Commons IO提供了流支持工具来执行复制。(顺便问一下,将文件放入InputStream中是什么意思?你能给我们看看你的代码吗?)

编辑:

好的,你想对项目内容做什么? 有一个item.get()方法返回一个字节数组中的整个内容。

编辑2:

item.getSize()将返回上传的文件大小


我目前将文件保存到数据库中的Blob字段,并将其作为二进制流(输入为InputStream)发送,现在我需要将InputStream转换为字节数组,因为我需要对数据进行签名,而该函数仅接受字节数组。 - ChronoXIII
1
item.get()会返回字节数组,就像我之前提到的那样。除非你正在处理几MB的图像,否则不必担心性能和大小。 - akarnokd
这可能是因为图片是由用户上传的,而我似乎找不到在服务器端自动裁剪图像的方法。:( - ChronoXIII
什么是自动修剪图像?get()将以byte[]的形式提供整个上传的文件(图像)。然后,您可以将其用于任何OutputStream.write(),再次将其包装到ByteArrayInputStream中等。 - akarnokd
1
我修改了这篇文章的解决方案,因为它的答案更适合我的当前设置。 - ChronoXIII

9

对于InputStream

org.apache.commons.io.IoUtils.toByteArray(inputStream).length()

对于可选的 < MultipartFile >

Stream.of(multipartFile.get()).mapToLong(file->file.getSize()).findFirst().getAsLong()

1
我无法找到IoUtils。我猜你是指org.apache.commons.io.IOUtils。 - ostmond
IOUtils.toByteArray(inputStream).length 对我没用。 - dev4life
是的。它与IOUtils不兼容。不确定为什么会出现这种情况以及如何有用。 - UM1979
返回的是一个字节数组,它有一个 length() 方法。我只是忘记加括号了。 - amstegraf
我们能不能只用inputStream.readAllBytes().length呢? - undefined

6
下面的函数适用于任何 InputStream。正如其他答案所提示的那样,你无法可靠地找到 InputStream 的长度而不经过读取,但与其他答案不同,你不应该尝试通过读入 ByteArrayOutputStream 将整个流保留在内存中,也没有理由这样做。相反,你应该优先依赖其他 API 来获取流大小,例如使用 File API 获取文件的大小。
public static int length(InputStream inputStream, int chunkSize) throws IOException {
    byte[] buffer = new byte[chunkSize];
    int chunkBytesRead = 0;
    int length = 0;
    while((chunkBytesRead = inputStream.read(buffer)) != -1) {
        length += chunkBytesRead;
    }
    return length;
}

针对不同类型的 InputStream,选择一个合理的值作为 chunkSize。例如,从磁盘读取时,使用太小的 chunkSize 不够高效。


我同意@Samuel的观点,这是从InputStream获取文件长度的正确方法。 - MFH

4

您可以使用Utils.java中的getBytes(inputStream)方法来获取InputStream的大小,请参考以下链接:

从InputStream获取字节


这个答案也应该是正确的吧?这个解决方案有什么问题吗? - Arlyn
4
如果您打算使用输入流,它已经被读取了。 - Amber

2
如果您需要将数据流式传输到另一个对象,但该对象不允许您直接确定大小(例如javax.imageio.ImageIO),则可以在InputStream中包装CountingInputStream(Apache Commons IO),然后读取大小:
CountingInputStream countingInputStream = new CountingInputStream(inputStream);
// ... process the whole stream ...
int size = countingInputStream.getCount();

2

1
如果您知道您的InputStream是一个FileInputStream或者ByteArrayInputStream,您可以使用一些反射技巧来获取流大小而不需要读取全部内容。以下是一个示例方法:
static long getInputLength(InputStream inputStream) {
    try {
        if (inputStream instanceof FilterInputStream) {
            FilterInputStream filtered = (FilterInputStream)inputStream;
            Field field = FilterInputStream.class.getDeclaredField("in");
            field.setAccessible(true);
            InputStream internal = (InputStream) field.get(filtered);
            return getInputLength(internal);
        } else if (inputStream instanceof ByteArrayInputStream) {
            ByteArrayInputStream wrapper = (ByteArrayInputStream)inputStream;
            Field field = ByteArrayInputStream.class.getDeclaredField("buf");
            field.setAccessible(true);
            byte[] buffer = (byte[])field.get(wrapper);
            return buffer.length;
        } else if (inputStream instanceof FileInputStream) {
            FileInputStream fileStream = (FileInputStream)inputStream;
            return fileStream.getChannel().size();
        }
    } catch (NoSuchFieldException | IllegalAccessException | IOException exception) {
        // Ignore all errors and just return -1.
    }
    return -1;
}

我相信这可以扩展支持其他输入流。


如果你能够承受重新创建inputStream并等待它,你可以读取所有内容以获取其大小作为这些操作的备选方案。 - android developer

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