【问题标题】:Incorrect File download through Multithreading in Java using RandomAccessFile使用 RandomAccessFile 通过 Java 中的多线程下载不正确的文件
【发布时间】:2014-05-09 16:09:15
【问题描述】:

尊敬的前辈您好:)

我的目标: 在给定一个 URL 的情况下,通过在 Java 中使用多线程下载一个 URL 资源,即将一个文件下载到多个部分(很像 IDM 的做法)并在末尾下载,将它们全部合并为 1 个最终文件。例如,如果一个图像是 260KB,我想将它下载到 2 个线程中,1st Thread 应该从 0KB 下载到 130KB2nd Thread 应该从 131KB to 260KB 下载。 p>

技术使用: Java、RandomAccessFile、多线程、InputStreams

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

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

  2. 有时会下载 260KB,但文件已损坏,我无法打开图像。

请帮助我。整个星期以来我已经尝试了很多,但我似乎无法理解这个问题。

初始代码

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

&现在,

线程类

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

【问题讨论】:

  • Android 所以没有适合你的 Java 7,对吗?
  • @fge 抱歉我没听懂...?
  • 我的意思是,你可以使用新的文件 API 吗?
  • @fge 当然可以。我正在研究 Java7。

标签: java multithreading inputstream httpurlconnection java-io


【解决方案1】:

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

【解决方案2】:

首先:使用ExecutorService 代替原始线程;您将更轻松地管理您的工作。

第二:为什么每次文件大小/2?更好的(恕我直言)是使用固定的块大小。

第三:使用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);
}

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

【讨论】:

  • 好老板。我现在就试试这个方法。万分感谢! ;)
【解决方案3】:

您正试图从多个线程写入同一个 file 对象。当您 seek() 或 write() 时,它只有一个视图来说明执行的位置。

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

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

【讨论】:

  • 你是一个救生员。你解决了。 :) 还有一件事,你能详细说明问题是什么吗?我无法理解“当您 seek() 或 write() 时,它只有一个视图可以查看执行的位置。”。
  • @deep_ecstasy 你有一个文件,你在一个线程中寻找(0)所以文件跳转到0,然后在另一个线程中你寻找(n)所以它跳转到n,现在你写即使你是从第一个线程开始的,它也会在n 之后。之后您编写的所有内容都将是从两个线程中下载的内容,顺序有点随机。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-18
  • 1970-01-01
  • 2011-03-25
  • 2022-11-02
  • 1970-01-01
相关资源
最近更新 更多