使用RandomAccessFile通过Java中的多线程下载文件不正确

时间:2014-03-30 09:57:50

标签: java multithreading inputstream httpurlconnection java-io

你好我尊敬的老年人:)

我的目标:通过在Java中使用多线程来下载URL资源,即使用URL中的多线程,即将单个文件下载到多个部分(非常类似于IDM的工作方式)&在下载结束时,将所有这些组合成1个最终文件。例如,如果一个Image是260KB,我想将它下载到2个线程中,1st Thread应该从 0KB下载到130KB & 2nd Thread应从131KB to 260KB下载。

技术使用: Java,RandomAccessFile,MultiThreading,InputStreams

问题:此代码适用于1个线程,即如果我运行从 0到260KB 的单个线程。但是当我尝试以块的形式下载它时,会发生以下错误:

  1. 它将下载额外的垃圾KB,即对于260KB文件,将下载300 + KB。

  2. 有时确实下载了260KB,但文件已损坏,我无法打开图片。

  3. 请帮帮我。自整个星期以来我都尝试过很多次&我似乎无法理解这个问题。

    初始代码

    void InitiaeDownload
    {
    
    
            HttpURLConnection uc = (HttpURLConnection) url.openConnection();
            uc.connect();
            long fileSize = uc.getContentLengthLong();
            System.out.println("File Size = "+ fileSize );
            uc.disconnect();
    
    //------------------
    
            long chunkSize = (long) Math.ceil(fileSize/2);
    
            long startFrom = 0;
            long endRange = (startFrom + chunkSize) - 1;
    
            System.out.println("Chunk Size = " + chunkSize);
            System.out.println("Part 1 :: Start = " + startFrom + "\tEnd To = " + endRange);
    
            Thread t1 = new MyThread(url, file, startFrom, endRange);
            t1.start();
    
            startFrom += chunkSize;
            long temp = endRange + chunkSize;
            endRange = temp + (fileSize-temp);  //also add remaining bytes
    
            System.out.println("Part 2 :: Start = " + startFrom + "\tEnd To = " + endRange );
    
            Thread t2 = new MyThread(url, file, startFrom, endRange);
            t2.start();
    }
    

    &安培;现在,

    THREAD CLASS

    class MyThread extends Thread {
    
        private URL url;
        private long startFrom;
        private long range;
        private InputStream inStream;
        private RandomAccessFile file;
    
    
        public MyThread(URL url, RandomAccessFile file, long startFrom,  long range)    //parameterized constructor
        {
    
            this.url = url;
            this.file = file;
            this.startFrom = startFrom;
            this.range = range;
    
        }
    
        public void run() {
    
            System.out.println("Thread Running..");
    
            Thread.currentThread().setPriority(MAX_PRIORITY);
    
            System.setProperty("http.proxyHost", "192.168.10.50");
            System.setProperty("http.proxyPort", "8080");   
    
            HttpURLConnection uc = null;
            try  {
    
                uc = (HttpURLConnection) url.openConnection();
    
                uc.setRequestProperty("Range", "bytes="+startFrom+"-"+range);
                uc.connect();
    
                inStream = uc.getInputStream();
                System.out.println("Starting Download");
    
                int bytesRead = 0;
                byte[] buffer = new byte[ (int) (range-startFrom) ];
    
    
                file.seek(startFrom);   //adjusted start of file
    
                while( (bytesRead = inStream.read(buffer) ) != -1 ) {
    
                    file.write(buffer, 0, bytesRead);
                }
    
                System.err.println("Download Completed!");
                uc.disconnect();
            }
            catch(IOException e) {
                System.err.println("Exception in " + Thread.currentThread().getName() + "\t Exception = " + e );
            }           
        }  ///END OF run()  
    } ////END OF MyThread Class
    

3 个答案:

答案 0 :(得分:2)

您正尝试从多个线程写入相同的file对象。当你搜索()或写()时,它只有一个视图,表明它在哪里。

如果你想同时在不同的地方写同一个,每个线程必须拥有它自己的RandomAccessFile对象,即使它们都指向同一个底层文件。

顺便说一句:更改IO绑定进程的线程优先级可能会做的很少(除非您是管理员,否则没有)

答案 1 :(得分:1)

首先:使用ExecutorService代替原始线程;你将更容易管理你的工作。

第二:为什么每次文件大小/ 2?更好(imho)将使用固定的块大小。

第三:使用FileChannel并以块的形式映射文件(使用FileChannel.open()获取频道)。

四:使用java.nio和try-with-resources,因为你使用Java 7;)

以下是如何从偏移1000(包含)写入文件中的偏移2000(不包括);请注意,文件大小会根据需要进行扩展!

// "channel" is your FileChannel.
// Argument to the worker: the channel, the offset, the size to write

final ByteBuffer buf = channel.map(FileChannel.MapMode.READ_WRITE, 1000L, 1000L);

try (
    final InputStream in = uc.getInputStream();    
    final ReadableByteChannel inChannel = Channels.newChannel(in);
) {
    inChannel.read(buf); // returns the number of bytes read
    channel.force(false);
}

这基本上就是你的一个工人的内容。

答案 2 :(得分:0)

要接收文件,文件必须至少是您说话位置的长度。因此,您必须首先使用结果大小创建文件,然后根据您的搜索位置覆盖数据。