确定 InputStream 的大小

Posted

技术标签:

【中文标题】确定 InputStream 的大小【英文标题】:Determine the size of an InputStream 【发布时间】:2010-11-10 07:10:01 【问题描述】:

我目前的情况是:我要读取一个文件并将内容放入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 包。

【问题讨论】:

【参考方案1】:

如果不读取流,您无法确定流中的数据量;但是,您可以询问文件的大小:

http://java.sun.com/javase/6/docs/api/java/io/File.html#length()

如果这不可能,您可以将从输入流中读取的字节写入ByteArrayOutputStream,它会根据需要增长。

【讨论】:

在我的场景中,输入流包含来自 HTML 表单的上传文件,我无法获取文件大小,因为我没有从硬盘驱动器加载文件。 如果您的输入流支持 mark(),您可以在开头标记并完全通读它 - 然后 reset() 并开始处理它。 该文件是一个图像(可能很大),因此通过流读取两次会导致性能问题,不是吗? 试试看。如果速度很慢,您就会遇到性能问题。否则,你不会。就这么简单。 @ChronoXIII:是的。这就是为什么我要求提供一个小代码示例,以便我们可以看到您的场景。如果您的图像已经在内存中(感谢 fileupload 或其他),那么它会打开更多选项。【参考方案2】:

我会读入ByteArrayOutputStream,然后调用toByteArray() 来获取结果字节数组。您不需要提前定义大小(尽管它可能是一种优化如果您知道的话。在很多情况下您不会)

【讨论】:

我似乎发现 inputstream 有一个 toString() 方法,然后我可以调用一个 getBytes() 从字符串中创建一个字节数组。我想知道这样做是否有任何性能问题? 您需要小心字节/字符转换。这最初是一个字节流吗?如果它包含字符,它们是如何编码的等等。如果你通过网络连接获得这些,我怀疑这是你的主要性能瓶颈,我不会担心转换开销 我目前将带有图像的输入流作为二进制流读取到数据库中,这似乎工作得很好,因为我可以在后面读回文件并且它仍然是图像 是的。你得到完整的原始字节数组,没有转换,没有问题。 如果你想要的只是大小,这真的不是内存效率低吗?【参考方案3】:

我只是想补充一点,Apache Commons IO 具有流支持实用程序来执行复制。 (顺便说一句,将文件放入输入流是什么意思?你能告诉我们你的代码吗?)

编辑:

好的,你想对项目的内容做什么? 在一个字节数组中有一个 item.get() which returns 整个东西。

编辑2

item.getSize() 将返回上传的file size。

【讨论】:

我目前将文件保存到数据库中的 blob 字段中,并将其作为二进制流(作为输入流输入)发送,现在我需要字节数组中的输入流,因为我需要进行签名数据和函数只需要字节数组。 正如我所提到的, item.get() 为您提供字节数组。除非您处理几 MB 图像,否则不必担心性能大小。 可能是这种情况,因为图像是由用户上传的,我似乎找不到在服务器端自动修剪图像的方法。 :( 自动修剪图像是什么意思? get() 会将整个上传的文件(图像)作为字节 [] 提供给您。然后你继续在任何 OutputStream.write() 上使用它,再次将它包装到 ByteArrayInputStream,等等。 我将解决方案更改为这篇文章,因为它的答案更适合我当前的设置【参考方案4】:

您可以使用 Utils.java 的 getBytes(inputStream) 获取 InputStream 的大小,请查看以下链接

Get Bytes from Inputstream

【讨论】:

这不应该也是一个正确的答案吗?这个解决方案有什么问题吗? 如果您打算使用输入流,它已经被读取了。【参考方案5】:

这是一个非常古老的线程,但当我在谷歌上搜索该问题时,它仍然是第一个弹出的内容。所以我只想添加这个:

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

为我工作。而且比这里的其他答案简单得多。

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

请注意,虽然 @code InputStream 的某些实现会返回 * 流中的总字节数,很多不会。

【讨论】:

我认为这不准确。来自 Javadocs:“请注意,虽然 InputStream 的某些实现将返回流中的字节总数,但许多不会。使用此方法的返回值来分配一个旨在保存此中所有数据的缓冲区是不正确的。溪流。”所以它可能在您的虚拟机上运行,​​但可能无法在其他人的虚拟机上运行。 docs.oracle.com/javase/7/docs/api/java/io/… 哦,精神病。不错的收获!我想这个技巧对于需要可移植的代码并不好。我正在用于一个学校项目,所以它对我有用。不过谢谢,很高兴知道未来! 当您将所有数据都保存在内存中时,就像在 ByteArrayInputStream 中一样。 这是错误的!正如documentation 所说的available() 方法“请注意,虽然 InputStream 的某些实现会返回流中的总字节数,但很多不会。使用此方法的返回值来分配一个缓冲区旨在保存此流中的所有数据。" 答案有误导性,那android docs呢,有明确的说明:“注意这个方法提供的保证很弱以至于不是很有用在实践中”。【参考方案6】:

使用这个方法,你只需要通过InputStream

public String readIt(InputStream is) 
    if (is != null) 
        BufferedReader reader = new BufferedReader(new InputStreamReader(is, "utf-8"), 8);

        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) 
            sb.append(line).append("\n");
        
        is.close();
        return sb.toString();
    
    return "error: ";

【讨论】:

-1,这假定 InputStream 始终是一个字符串(不是必需的,因为它可能是二进制数据),并且在任何情况下它都不会返回流的大小,这就是问的问题。此外,它会在数据中添加额外的字节 (\n),因此 sb.length() 也将不准确。 好的,我明白了,所以当您知道您希望收到String 流时,这很有用。 \n 非常需要,因为您必须为收到的输入提供某种格式,我不想要一个很长的 String,所以即使是那些 \ncounts。【参考方案7】:

当显式处理 ByteArrayInputStream 时,与此页面上的某些 cmets 相反,您可以使用 .available() 函数来获取大小。只需在开始阅读之前完成即可。

来自 JavaDocs:

返回可以读取(或跳过)的剩余字节数 结束)从此输入流。返回的值为 count - pos,即 是要从输入缓冲区读取的剩余字节数。

https://docs.oracle.com/javase/7/docs/api/java/io/ByteArrayInputStream.html#available()

【讨论】:

【参考方案8】:
    try 
        InputStream connInputStream = connection.getInputStream();
     catch (IOException e) 
        e.printStackTrace();
    

    int size = connInputStream.available();

int 可用 () 返回可以从此输入流中读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞。下一次调用可能是同一个线程或另一个线程。单次读取或跳过这么多字节不会阻塞,但可能会读取或跳过更少的字节。

InputStream - Android SDK | Android Developers

【讨论】:

这是一个错误,所以我发现 available() 函数只返回活动数据大小而没有洞流长度。【参考方案9】:

对于输入流

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

对于可选的

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

【讨论】:

我在任何地方都找不到 IoUtils。我猜你的意思是 org.apache.commons.io.IOUtils, IOUtils.toByteArray(inputStream).length 对我不起作用。 是的。它不适用于 IOUtils。不知道为什么这种方法以及它如何有用。 返回一个字节数组,有length()方法。我只是忘了添加括号【参考方案10】:

如果您知道您的InputStreamFileInputStreamByteArrayInputStream,则可以使用一点反射来获得流大小无需阅读全部内容。这是一个示例方法:

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 并等待它的费用,您可以阅读所有内容以获取其大小作为所有这些的后备【参考方案11】:

下面的函数应该适用于任何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;

chunkSize 选择一个适合InputStream 类型的合理值。例如。从磁盘读取时,chunkSize 的值太小会效率不高。

【讨论】:

【参考方案12】:

如果您需要将数据流式传输到另一个不允许您直接确定大小的对象(例如javax.imageio.ImageIO),那么您可以将您的InputStream 包装在CountingInputStream(Apache Commons IO)中,然后读取大小:

CountingInputStream countingInputStream = new CountingInputStream(inputStream);
// ... process the whole stream ...
int size = countingInputStream.getCount();

【讨论】:

以上是关于确定 InputStream 的大小的主要内容,如果未能解决你的问题,请参考以下文章

从 OutputStream 创建 InputStream 的最有效方法

从 OutputStream 创建 InputStream 的最有效方法

如何读取服务器套接字 JAVA 中的所有 Inputstream

四大IO抽象类

Http

如何在java中识别InputStream的结束