多线程下载图片,输出流用的是BufferedOutputStream!
如果线程的启动顺序是1、2、3、4、5....
那么下载的图片是没有问题,可以正常打开。
当然线程的启动顺序我们是无法控制的
顺序一乱,那么下载的图片就会有问题!
无法预览,甚至只有某一部份是正常的
请问,这种问题怎样解决...?以下是多线程下载图片的代码:
public class DemoThread extends Thread {

private DownloadTest dts = null;
private int id = 0;
private long startPos = 0;
private long endPos = 0;
private CountDownLatch latch = null;
private BufferedOutputStream output = null;
/**
 * 
 * @param dts DownloadTest的引用
 * @param latch CountDownLatch对象
 * @param id 线程ID
 * @param startPos 下载的开始位置
 * @param endPos 下载的结束位置
 * @param output 输出流对象
 * @throws FileNotFoundException 
 */
public DemoThread(DownloadTest dts, CountDownLatch latch, int id, long startPos, long endPos, BufferedOutputStream output){
this.dts = dts;
this.latch = latch;
this.id = id;
this.startPos = startPos;
this.endPos = endPos;
this.output = output;

}

@Override
public void run() {
System.out.println("线程 " + id + " 启动...");
HttpURLConnection httpConn = null;
InputStream input = null;
BufferedOutputStream output1 = null;
long count = 0;
long threadDownloadLength = endPos - startPos;
try{ httpConn = (HttpURLConnection) dts.getUrl().openConnection();
// 设置连接超时时间为10000ms
httpConn.setConnectTimeout(10000);
// 设置读取数据超时时间为10000ms
httpConn.setReadTimeout(10000);

if(startPos < endPos){
httpConn.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);
httpConn.setRequestProperty("Accept","image/gif,image/x-xbitmap,application/msword,*/*");
System.out.println("线程  "+ id + " 从 " + startPos + " =====> " + endPos);

//判断http status是否为 206 Partial Content或者200 OK 
if(httpConn.getResponseCode() == HttpURLConnection.HTTP_OK || 
httpConn.getResponseCode() == HttpURLConnection.HTTP_PARTIAL){

input = httpConn.getInputStream();

int byteread = 0;
byte[] b = new byte[1024]; while((byteread = input.read(b)) > 0){
output.write(b, 0, byteread); count += byteread;

} if(count >= threadDownloadLength){
System.out.println(id+" OK.");
}

output1.flush();
output.flush();
} else {
System.out.println("线程 "+ id + ": 状态码=" + httpConn.getResponseCode()
+ ", 错误消息=" + httpConn.getResponseMessage());
}
}
System.out.println("线程 " + id + " 下载完成!");
// 子线程下载完指定的数据后将计数器减1
latch.countDown();

} catch (java.net.SocketException se){
se.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(null != input) input.close();
if(null != output1) output1.close();
} catch (IOException e) {
e.printStackTrace();
}
if(null != httpConn) httpConn.disconnect();
}
}
}

解决方案 »

  1.   

    你访问服务器的时候  服务器不是会返回给你一些head信息吗
      

  2.   

    head信息,我确实没有获取!文件大小都是一样的!
    获取head信息设置到哪...?
    就是下载完后打开有问题,如果是rar,则是:无法预料的压缩文件末端。。
    如果是图片,则是:无法预览,甚至只有某一部份是正常的
      

  3.   

    所有文件都一样大,那么你下载的时候创建一个固定大小的文件,
    例如:100K
    5个线程读取,那每个分20K
    那么thread-1~thread-5分别分派20K,计算好每个线程的起始和结束的位置,
    就如迅雷一样。每个线程做自己的事互不干涉
      

  4.   

    这个是P2P下载的思想,分段下载。
    1. 取得这个图片的大小
    2. 创建一个图片大小的文件
    3. 分配每个线程下载的字节数量,计算出每个线程要写文件的位置
    4. 使用RandomAccessFile随机定位,每个线程从自己的位置处开始写入已下载到的数据
      

  5.   

    我刚查了些资料,我觉得外部是没办法让IE按照你的方式来下载,你可以在后台让多个线程分段读文件,但是写的话可能只能用一个线程了。IE的多线程下载都是它内部处理的,程序中可能无法控制。(这只是个人的认识,有待确认)
      

  6.   

    不对,我的代码中就是只有一个写入流呀,BufferedOutputStream
      

  7.   

    你每个线程看到的OutputStream应该是不同的吧?也就是只是分片的Stream吧?那么你先将这些分片Stream对应为文件时,你看看每个线程下载的是否者正确吧
      

  8.   

    不太明白。你现在是做下载客户端还是下载服务器?如果是IE下载,它本身只支持单线程,也不会发Range信息到服务器上的。
      

  9.   

    下载:弹出IE的下载对话框,程序中采用的是BufferedOutputStream!我上面的下载代码,可以看一下...
      

  10.   

    没有实际运行类,看不出来。我还是不明白你的多线程是怎么来的,你现在是写Web服务器上面的类吧?
    、如果你是如果是IE下载,那么只是单线程。如果使用工具下载,那也是多次请求啊。正常来说你不需要自己启动线程的,我还是不明白你的意思
      

  11.   

    是Web服务器上面的...那下在面的代码就应该明白了吧!
    public class DownloadTest {

    // 下载的URL地址
    private URL url = null;
    // 开启的线程数
    private int threadNum = 5;
    // 每个线程下载的范围
    private long subLen = 0;
    // 下载的文件总长度
    private long fileLength = 0;
    // 要下载的目标文件
    private String localFile = "e:\\yyy.rar.tag";
    // 临时文件记录下载任务的信息
    private String localTmp = "e:\\yyy.tmp";

    private String range = null;
    private HttpServletResponse response = null;

    public void download(String urlStr){

    // 初始化计数器为开启的线程数
    CountDownLatch latch = new CountDownLatch(this.getThreadNum());
    // 子线程对象
    DemoThread[] demoThread = new DemoThread[this.getThreadNum()];
    // 使用线程池管理线程
    ExecutorService pool = Executors.newCachedThreadPool();
    // 每段线程下载的开始位置
    long[] startPos = new long[this.getThreadNum()];
    // 每段线程下载的结束位置
    long[] endPos = new long[this.getThreadNum()];

    BufferedOutputStream output = null;
    HttpURLConnection httpConn = null;
    InputStream input = null;
    try {
    this.setUrl(new URL(urlStr));
    long begin = 0, end = 0;

    // 判断是否有带Range参数
    if(null == this.getRange()){
    begin = 0;
    end = this.getFileLength();
    } else {
    String Range[] = this.getRange().split(",");
    System.out.println("Range[0]:"+Range[0]);
    String rangePos[] = Range[0].split("-");
    if("".equals(rangePos[0])){
    begin = this.getFileLength() - Long.parseLong((rangePos[1].toString()));
    }else{
    begin = Long.parseLong((rangePos[0].toString()));
    }
    if(rangePos.length != 1){
    if("".equals(rangePos[1])){
    end = this.getFileLength();
    }else{
    end = Long.parseLong((rangePos[1].toString()));
    if("".equals(rangePos[0])){
    end = this.getFileLength();
    }
    }
    }else{
    end = this.getFileLength();
    }
    }

    // 每块线程负责下载的长度
    this.setSubLen((end - begin) / this.getThreadNum());
    System.out.println("subLen: "+this.getSubLen());
    int mod = (int) ((end - begin) % this.getThreadNum());
    System.out.println(this.getLocalFile());
    String thisFileName = this.getLocalFile().substring(0,this.getLocalFile().length());
    //String fileName = thisFileName.substring(4,thisFileName.length());

    this.getResponse().setContentType("application/x-msdownload;charset=utf-8");
    this.getResponse().reset(); // 清除缓冲中的数据
    this.getResponse().setHeader("Content-Disposition","attachment;filename="+thisFileName);
    this.getResponse().setHeader("Content-Length",String.valueOf(this.getFileLength()));
    this.getResponse().setHeader("Location", this.getUrl().toString());

    System.out.println("begin:"+begin+"   end:"+end+"   mod:"+mod);
    long currtime = System.currentTimeMillis();

    output = new BufferedOutputStream(this.getResponse().getOutputStream());
    // 分多个线程下载文件
    for(int i = 0; i < this.getThreadNum(); i ++){
    startPos[i] = this.getSubLen() * i ;
    if(null == this.getRange()){
    if(i == this.getThreadNum() - 1){
    endPos[i] = this.getFileLength();
    } else {
    endPos[i] = this.getSubLen() * (i + 1) -1; 
    }
    }else{
    startPos[i] = begin + startPos[i] + i;
    if(i == this.getThreadNum() - 1){
    endPos[i] = end;
    }else{
    endPos[i] = startPos[i] + this.getSubLen() ;
    }
    }
    System.out.println("startPos["+i+"]:"+startPos[i]+" endPos["+i+"]:"+endPos[i]);
    DemoThread dt = new DemoThread(this, latch, i+1, startPos[i], endPos[i], output);
    demoThread[i] = dt;
    pool.execute(dt);

    }
    // 阻塞主线程,等待CountdownLatch信号为0,表示所有子线程都结束。
    latch.await();
    pool.shutdown();
    this.getResponse().flushBuffer();//将缓冲中的数据写入到客户端
    System.out.println("time: "+ (System.currentTimeMillis() - currtime) );

    mergeFile();

    } catch (MalformedURLException mue) {
    mue.printStackTrace();
    } catch (Exception e) {
    e.printStackTrace();
    }finally{
    try {
    if(null != httpConn) httpConn.disconnect();
    if(null != output) output.close();
    if(null != input) input.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    }
      

  12.   

    我已经说了,如果是写Web服务器,根本没有任何多线程的必要。如果你非常想用多线程,那
    output = new BufferedOutputStream(this.getResponse().getOutputStream());也是错误的。第二个线程不能用这个output,因为第二个线程必须等待第一个线程全部下载完成并写入output后,第二个线程才可以开始往这个output中写入。如果你非常希望使用多线程,那么你应该为第二个线程至第N个线程传递每一个
    OutputStream os = new FileOutputStream(java.io.File.createTempFile("p", "s"));
    即每个线程都写入一个文件
    然后在download方法中,逐个调用dt.join(),然后再将这个临时File中的内容再写入第一个线程所看到的out中
      

  13.   

    没有测试,大致就是删除了CountDownLatch引用,定义一个
    Future<File>[] callables = new Future[this.getThreadNum()];File file = File.createTempFile("download", "tmp");
    DemoThread dt = new DemoThread(this, i+1, startPos[i], endPos[i], new BufferedOutputStream(new FileOutputStream(file)));然后把pool.execute修改为callables[i] = pool.submit(dt, file);删除了latch.await();并增加            for(int i = 0; i < this.getThreadNum(); i ++){
                 FileInputStream is = new FileInputStream(callables[i].get());
                 byte[] buff = new byte[4096];
                 int readed;
                 while((readed = is.read(buff)) > 0)
                 output.write(buff, 0, readed);
                 is.close();
                }
                output.close();
      

  14.   

    去掉CountDownLatch引用,那DemoThread类里面的 latch.countDown();
    不也是要去掉...?
      

  15.   

    去掉,因为其实外面这个线程其实必须按顺序等待这几个线程的完成。所以使用Future而不是CountDownLatch
      

  16.   

    这个是P2P下载的思想,分段下载。
    1. 取得这个图片的大小
    2. 创建一个图片大小的文件
    3. 分配每个线程下载的字节数量,计算出每个线程要写文件的位置
    4. 使用RandomAccessFile随机定位,每个线程从自己的位置处开始写入已下载到的数据