OutputSream.write太慢了

时间:2015-12-10 07:07:16

标签: java perl download outputstream fileoutputstream

我遇到了像这样的senerior:

我的项目有一个servlet来捕获来自perl的请求。请求是下载文件。请求是multipartRequest。

 @RequestMapping(value = "/*", method = RequestMethod.POST)
        public void tdRequest(@RequestHeader("Authorization") String authenticate,
                              HttpServletResponse response,
                              HttpServletRequest request) throws Exception
        {
    if (ServletFileUpload.isMultipartContent(request))
            {
                ServletFileUpload sfu = new ServletFileUpload();
                FileItemIterator items = sfu.getItemIterator(request);
                while (items.hasNext())
                {
                    FileItemStream item = items.next();
                    if (("action").equals(item.getFieldName()))
                    {
                        InputStream stream = item.openStream();
                        String value = Streams.asString(stream);

                        if (("upload").equals(value))
                        {
                            uploadRequest(items, response);
                            return;
                        }
                        else if (("download").equals(value))
                        {
                            downloadRequest(items, response);
                            return;
                        }

问题不在这里,它出现在downloadRequest()函数中。

    void downloadRequest(FileItemIterator items,
                             HttpServletResponse response) throws Exception
{ 
log.info("Start downloadRequest.......");
OutputStream os = response.getOutputStream();
File file = new File("D:\\clip.mp4");
            FileInputStream fileIn = new FileInputStream(file);
            //while ((datablock = dataOutputStreamServiceImpl.readBlock()) != null)
            byte[] outputByte = new byte[ONE_MEGABYE];
            while (fileIn.read(outputByte) != -1)
            {

                System.out.println("--------" + (i = i + 1) + "--------");
                System.out.println(new Date());
                //dataContent = datablock.getContent();
                System.out.println("Start write " + new Date());
                os.write(outputByte, 0,outputByte.length);
                System.out.println("End write " + new Date());
                //System.out.println("----------------------");
            }
            os.close();
        }
    }

我尝试从文件中读取和写入1MB的块。但是,下载整个文件需要很长时间。 (我的情况是20分钟,文件为100MB)

我尝试使用sysout,我看到了这样的结果:

前几个块可以快速读取,写入数据:

--------1--------
Mon Dec 07 16:24:20 ICT 2015
Start write Mon Dec 07 16:24:20 ICT 2015
End write Mon Dec 07 16:24:21 ICT 2015
--------2--------
Mon Dec 07 16:24:21 ICT 2015
Start write Mon Dec 07 16:24:21 ICT 2015
End write Mon Dec 07 16:24:21 ICT 2015
--------3--------
Mon Dec 07 16:24:21 ICT 2015
Start write Mon Dec 07 16:24:21 ICT 2015
End write Mon Dec 07 16:24:21 ICT 2015

但是下一个块比前一个块慢

--------72--------
Mon Dec 07 16:29:22 ICT 2015
Start write Mon Dec 07 16:29:22 ICT 2015
End write Mon Dec 07 16:29:29 ICT 2015
--------73--------
Mon Dec 07 16:29:29 ICT 2015
Start write Mon Dec 07 16:29:29 ICT 2015
End write Mon Dec 07 16:29:37 ICT 2015

--------124--------
Mon Dec 07 16:38:22 ICT 2015
Start write Mon Dec 07 16:38:22 ICT 2015
End write Mon Dec 07 16:38:35 ICT 2015
--------125--------
Mon Dec 07 16:38:35 ICT 2015
Start write Mon Dec 07 16:38:35 ICT 2015
End write Mon Dec 07 16:38:48 ICT 2015

问题出在os.write()中 我真的无法理解outputStream是如何写的,为什么需要这么长时间呢?或者我犯了一些错误?

抱歉我的英语不好。我真的需要你的支持。提前谢谢!

这是来自客户端的perl代码

 # -----  get connected to download the file
        #
        $Response = $ua->request(POST $remoteHost ,
                        Content_Type          => 'form-data',
                        Authorization         => $Authorization,
                        'Proxy-Authorization' => $Proxy_Authorization ,

                        Content => [ DOS          => 1 ,
                                     action       => 'download' ,
                                     first_run    => 0 ,
                                     dl_filename  => $dl_filename ,
                                     delivery_dir => $delivery_dir ,
                                     verbose      => $Verbose ,
                                     debug        => $debug ,
                                     version      => $VERSION
                                   ]
                        );

        unless ($Response->is_success) {
                my $Msg = $Response->error_as_HTML;

                # Remove HTML tags - we're in a DOS shell!
                        $Msg =~ s/<[^>]+>//g;

                print "ERROR! SERVER RESPONSE:\n$Msg\n";
                print "$remoteHost\n\n" if $Options{'v'};
                Error "Could not connect to " . $remoteHost ;
        }

        my $Result2 = $Response->content();

        Error "Abnormal termination...\n$Result2" if $Result2 =~ /_APP_ERROR_/;

        open(F, ">$dl_filename") or Error "Could not open '$dl_filename'!";
        binmode F; # unless $dl_filename =~ /\.txt$|\.htm$/;
        print F $Result2;
        close F;
        print "received.\n";

        }

2 个答案:

答案 0 :(得分:2)

一个问题是fileIn.read(outputByte)可以读取随机字节数,而不仅仅是完整的outputByte。你读了几KB,然后你存储了1MB,而且你的磁盘空间不足。试试这个,注意“readed”参数。

  void downloadRequest(FileItemIterator items,
                             HttpServletResponse response) throws Exception
    { 
    log.info("Start downloadRequest.......");
    OutputStream os = response.getOutputStream();
    File file = new File("D:\\clip.mp4");
                FileInputStream fileIn = new FileInputStream(file);
                //while ((datablock = dataOutputStreamServiceImpl.readBlock()) != null)
                byte[] outputByte = new byte[ONE_MEGABYE];
                int readed =0;
                while ((readed =fileIn.read(outputByte)) != -1)
                {

                    System.out.println("--------" + (i = i + 1) + "--------");
                    System.out.println(new Date());
                    //dataContent = datablock.getContent();
                    System.out.println("Start write " + new Date());
                    os.write(outputByte, 0,readed );
                    System.out.println("End write " + new Date());
                    //System.out.println("----------------------");
                }
                os.close();
            }
        }

答案 1 :(得分:2)

看起来您的下载性能越来越慢,您进入下载的距离越远。你可以在每个块的一个或更少的时间内开始,在块72中,每个块为7秒以上,在块128中,每个块为13秒。

服务器端没有任何内容可以解释这一点。相反,它有客户端的“气味”做错了。我的猜测是客户端正在从套接字读取数据到内存中的数据结构,并且该数据结构(可能只是StringStringBufferStringBuilder)正在获取越来越大。要么扩大它的时间越来越大,或者你的内存占用越来越大,GC的使用时间越来越长。 (或两者兼而有之。)

如果您向我们展示了客户端代码.....

<强>更新

正如我所怀疑的那样,这行代码将把整个内容读入字符串构建器的Perl等效内容,然后再将其转换为字符串。

   my $Result2 = $Response->content();

根据它在引擎盖下的实现方式,当构建器耗尽缓冲区空间并需要扩展时,这将导致重复复制数据。根据Perl为此采用的缓冲区扩展策略,它可能会产生O(N^2)行为,其中N是您要传输的文件的大小。 (证据表明你没有得到O(N)行为......)

如果您想要更快的下载,则需要在客户端流式传输数据。以块的形式读取响应内容并将其写入输出文件。 (我不是Perl专家,因此我无法为您提供代码。)这也会减少客户端的内存占用量......如果文件大小增加,这可能很重要。