简体   繁体   English

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

[英]Incorrect File download through Multithreading in Java using RandomAccessFile

Hello My Respected Seniors :) 你好,我尊敬的老年人:)

My Goal: Download a URL Resource, given a URL, by using Multi-Threading in Java, ie download a single file into multiple pieces (much like how IDM does) & at the end of download, combine all of them to 1 final file. 我的目标:通过使用Java中的多线程来下载给定URL的URL资源,即将单个文件下载为多个文件(类似于IDM的方式),并在下载结束时将所有文件合并为一个最终文件。 For instance, if an Image is of 260KB, I want to download it into 2 threads for which, 1st Thread should download from 0KB to 130KB & 2nd Thread should download from 131KB to 260KB . 例如,如果一个映像为260KB,我想将其下载到2个线程中, 1st Thread应从0KB下载到130KB2nd Thread应从131KB to 260KB下载到131KB to 260KB

Technology Using: Java, RandomAccessFile, MultiThreading, InputStreams 使用的技术: Java,RandomAccessFile,多线程,InputStreams

Problem: This code works for 1 thread, ie If I run a single thread that starts from 0 to 260KB . 问题:此代码适用于1个线程,即,如果我运行一个从0到260KB开始的线程。 But when I try to download it in chunks, following errors occur: 但是,当我尝试分批下载时,会发生以下错误:

  1. It will download extra garbage KBs, ie for a 260KB file, 300+KB will be downloaded. 它将下载额外的垃圾KB,即对于260KB文件,将下载300 + KB。

    OR 要么

  2. Sometimes exact 260KB are downloaded, but file is corrupt, I can't open the Image. 有时,确切的260KB已下载,但文件已损坏,我无法打开图像。

Please help me. 请帮我。 I've tried alot since the whole week & I can't seem to understand the problem. 自整周以来,我已经尝试了很多次,但似乎无法理解问题所在。

INITIAL CODE 初始代码

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();
}

& Now, 现在

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

You are attempting to write to the same file object from multiple threads. 您试图从多个线程写入同一file对象。 When you seek() or write() it has only one view of where this is being performed. 当您seek()或write()时,它只对执行位置有一个看法。

If you want to write in different places to the same at once, each thread must have it's own RandomAccessFile object even if they all point to the same underlying file. 如果要一次在不同的位置写入相同的内容,则每个线程都必须具有其自己的RandomAccessFile对象,即使它们都指向同一个基础文件也是如此。

BTW: Changing the thread priority of an IO bound process is likely to do very little (and nothing unless you are an administrator) 顺便说一句:更改IO绑定进程的线程优先级可能做的很少(除非您是管理员,否则什么也不会做)

First: use an ExecutorService instead of raw threads; 首先:使用ExecutorService代替原始线程; you will have an easier time managing your work. 您将可以更轻松地管理工作。

Second: why file size / 2 each time? 第二:为什么每次文件大小为2? Better (imho) would be to use a fixed chunk size. 更好的(恕我直言)将使用固定的块大小。

Third: use a FileChannel and map your file in chunks (use FileChannel.open() to obtain the channel). 第三:使用FileChannel并成块地映射文件(使用FileChannel.open()获取通道)。

Four: use java.nio and try-with-resources since you use Java 7 ;) 四:由于使用Java 7,因此请使用java.nio和try-with-resources;)

Here is how you would write from offset 1000 (included) to offset 2000 (excluded) in a file; 这是在文件中从偏移量1000(包括)写入偏移量2000(不包括)的方式。 note that the file size is expanded as needed! 请注意,文件大小会根据需要扩展!

// "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);
}

That is basically the content of one of your workers. 这基本上就是您的一名工人的内容。

To seak withing a file, the file must be at least the length of you speak position. 要隐藏文件,文件必须至少等于您说话的位置的长度。 So you have to first create the file with the resulting size and then override the data based on you seek positions. 因此,您必须首先创建具有结果大小的文件,然后根据您的搜索位置覆盖数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM