多线程随想录——多线程分块下载文件

参考资料

疯狂Java讲义IO操作篇,多线程篇。课后题讲义。

业务背景

1、启动程序时,检测任务列表是否有没有完成的下载任务,有的话,呈现出下载任务

2、有任务的话,继续下载,没有的话建立下载任务。

3、暂停下载,关闭下载任务的所有线程

3、开始下载,唤醒所有的下载任务。

基本步骤

讲义实现的步骤很明确,而且逻辑也很严谨。自己做主张,把讲义的代码改了改,发现不能改啊,改了会有好多麻烦。讲义的代码结构设计很棒。

1、建立远程连接,获取下载文件的大小。

2、根据大小和线程数,循环操作生成块实体。

3、根据块文件实体集合,产生相应的4个块文件,启动4个线程下载文件。

4、等待子线程结束,根据块文件实体集合的begin位置,合成一个文件。

扩展

1、当停止下载时,把下载信息序列化到文件中。

2、开始下载时,反序列化文件,开始下载。

实现节点:

在实现过程中,一方面是参照源码,另一方面根据实现的几个节点进行百度,看书。

1、查找如何新建线程,主线程如何等待子线程结束,再进行下一步。

2、文件如何分块,使用RandomAccessFile对象

3、建立HTTP连接,使用HttpUrlConnection对象


总结:针对一个业务流程,需要设计业务模型,然后根据业务模型,设计相应的代码结构,也就是说设计模式。针对业务实现的每个点要考虑到耦合关系,拆分关系,业务关联的关系,业务应该拆开,还是联合。这些地方很难。反而最后的选择使用的技术,代码实现较为简单。其中最难的过程是设计业务模型。



再往下全部是代码了,可以略过。

实体

public class PartModel implements Serializable {
    //不同jvm防止反序列化出错
    private static final long serialVersionUID = 1L;
    
    private transient static SnowflakeIdWorker sfiw = new SnowflakeIdWorker(0,1L);
//    使用雪花算法,防止ID碰撞
    private Long id  = sfiw.nextId();
    //字节开始位置
    private long begin;
    //字节长度
    private long length;
    //实际内容长度
    private long curentLength;

    private long end;

    public PartModel(){}

    public PartModel(long begin, long length, long currentLength){
        this.length = length;
        this.begin  = begin;
        this.curentLength  = currentLength;
    }
public class ResourceModel implements Serializable {
    private static final long serialVersionUID = 1L;
    private String sourcePath;
    private String sourceName;
    private List<PartModel> partList;
    private int threadCount;
    private int downState;
    private String targetName;
    private String targetPath;
    private long size = -1;
    private Date downloadDate = new Date();
    public ResourceModel(){}
    public ResourceModel(String sourcePath,String sourceName,String targetName,String targetPath){
        this.sourceName = sourceName;
        this.sourcePath = sourcePath;
        this.targetName = targetName;
        this.targetPath = targetPath;
    }

下载工具类

public class DownUtil {
    private ResourceModel rm;
    public DownUtil(ResourceModel rm){
        this.rm = rm;
    }
    public void downLoad(){
        RandomAccessFile targetRaf = null;

        try{
            //根据块文件集合,建立相应的线程
            if(rm.getPartList() != null){
                List<PartModel> list = rm.getPartList();
                int size = list.size();
                Thread[] threads = new Thread[size];
                for (int i=0;i<size;i++){
                    PartModel part = list.get(i);
                    RandomAccessFile ras =
                            new RandomAccessFile(rm.getTargetPath() + part.getId() + ".part","rw");
                    threads[i] = new DownThread(ras,part);
                }
                for (Thread th:threads) {
                    th.start();
                    //等待子线程完成
                    th.join();
                }

//                使用RandomAccessFile文件合并块文件,也可以使用OutputStream
                    targetRaf = new RandomAccessFile(rm.getTargetPath() + rm.getTargetName(),"rw");
                for (PartModel pm:list) {
                    targetRaf.seek(pm.getBegin());
                    FileInputStream fis = new FileInputStream(rm.getTargetPath() + pm.getId() + ".part");
                    byte[] bts = new byte[1024];
                    int line = 0;
                    while((line=fis.read(bts))!=-1){
                        targetRaf.write(bts,0,line);
                    }
                    fis.close();
                }
            }else{
                System.out.println("分块文件不存在!");
            }
        }
        catch (Exception ex){
            ex.printStackTrace();
        }finally {
            try{
                if(targetRaf!= null){
                    targetRaf.close();
                }
            }catch (Exception e){
                e.printStackTrace();
            }

        }

    }

}

下载线程类

public class DownThread extends Thread {
    private RandomAccessFile accessFile;
    private PartModel partModel;

    public DownThread(RandomAccessFile accessFile,PartModel part) {
        this.accessFile = accessFile;
        this.partModel = part;
    }

    @Override
    public void run() {

        InputStream is = null;
        HttpURLConnection conn = null;
        try {
            String path = "http://localhost:8099/shop/products/1/cs60006.png";
            URL url = new URL(path);
            conn = (HttpURLConnection) url.openConnection();
            long begin = partModel.getBegin();
            long end = partModel.getEnd();
            conn.setRequestProperty("Range", "bytes=" + begin + "-" + end);
            conn.connect();
             is = conn.getInputStream();
            byte[] buffer = new byte[1024];
            int perRead = 0;
            this.accessFile.seek(this.partModel.getCurentLength());
            while ((perRead = is.read(buffer)) != -1) {
                this.accessFile.write(buffer, 0, perRead);
                this.partModel.setCurentLength(this.partModel.getCurentLength() + perRead);
            }
            System.out.println(this.partModel.getId());
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            try{
                closeStream(is,conn,this.accessFile);
            }catch (Exception ex){
                ex.printStackTrace();
            }
        }
    }

    private void closeStream(InputStream is,HttpURLConnection conn,RandomAccessFile raf)throws  Exception {
        if(is != null){
            is.close();
        }
        if(conn != null){
            conn.disconnect();
        }
        if(raf != null){
            raf.close();
        }
    }


}

测试开始下载类

public class DownTest {
    private String serializableFile = "D:" + File.separator + "test.txt";

    /**
     * 生成资源对象
     *
     * @return
     */
    private ResourceModel createResuource() {
        ResourceModel model = new ResourceModel();
        String path = "http://localhost:8099/shop/products/1/cs60006.png";
        model.setSourcePath(path);
        model.setTargetName("downtest.png");
        model.setTargetPath("D:");
        model.setThreadCount(4);
        return model;
    }

    //测试方法
    public static void main(String[] args) throws Exception {

        DownTest dt = new DownTest();
        ResourceModel resourceModel = dt.createResuource();

        //URL资源定位,建立远程连接。
        URL url = new URL(resourceModel.getSourcePath());
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(5 * 1000);
        conn.setRequestMethod("GET");
        conn.setRequestProperty("Accept", "");
        conn.setRequestProperty("Accept-Language", "zh-CN");
        conn.setRequestProperty("Charset", "UTF-8");
        conn.connect();
//        获取文件大小
        long size = conn.getContentLength();
        resourceModel.setSize(size);
        //生成块文件集合
        resourceModel.setPartList(dt.createPartList(resourceModel));
        conn.disconnect();

        new DownUtil(resourceModel).downLoad();

        //        conn.setRequestProperty("Range","bytes=" + begin);
    }

    /**
     * 反序列生成对象
     *
     * @return
     */

    public ResourceModel refSeriableResource() {
        String path = this.serializableFile;
        ResourceModel rm = null;
        try {
            FileInputStream fis = new FileInputStream(path);
            ObjectInputStream ois = new ObjectInputStream(fis);
            rm = (ResourceModel) ois.readObject();
        } catch (Exception ex) {
            ex.printStackTrace();
            return rm;
        }
        return rm;
    }

    /**
     * 序列化对象到固定文件
     *
     * @param model
     */

    public void seriableResource(ResourceModel model) {
        if (model != null) {
            try {
                String path = this.serializableFile;
                OutputStream os = new FileOutputStream(path);
                ObjectOutputStream oos = new ObjectOutputStream(os);
                oos.writeObject(model);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


    }

    /**
     * 生成集合对象
     *
     * @param rm
     * @return
     */
    public List<PartModel> createPartList(ResourceModel rm) {

        if (rm.getPartList() == null || rm.getPartList().size() == 0) {
            List<PartModel> list = new ArrayList<>(rm.getThreadCount());
            int len = rm.getThreadCount();
            long length = rm.getSize() / len;
            for (int i = 0; i < len; i++) {
                PartModel part = new PartModel();
                long begin = i * length;
                part.setBegin(begin);
                part.setCurentLength(0);
                if (i == len - 1) {
                    part.setEnd(rm.getSize());
                } else {
                    part.setEnd(begin + length);
                }
                list.add(part);
            }
            return list;
        }
        return rm.getPartList();

    }


    /**
     * 测试序列化
     */
    @Test
    public void testSerializable() {
        ResourceModel model = new ResourceModel();
        model.setThreadCount(4);
        model.setTargetPath("targetPath");
        model.setTargetName("targetName");
        model.setSize(4000);
        model.setSourcePath("sourcepath");
        List<PartModel> list = new ArrayList<>(4);
        for (int i = 0; i < 4; i++) {
            PartModel part = new PartModel();
            long begin = i * 5444;
            part.setBegin(begin);
            part.setCurentLength(0);
            list.add(part);
        }
        model.setPartList(list);
        this.seriableResource(model);
    }

    /**
     * 测试反序列化
     */
    @Test
    public void testRefSerializable(){
        ResourceModel model = this.refSeriableResource();
        System.out.println(model.getSourceName());
    }







  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
Delphi线程池实现多线程FTP分段下载组件 by :renshouren mail:114032666@qq.com QQ:114032666 2019.10.05 使用的组件 1、TIdFTP Indy FTP客户端 2、TThreadsPool 线程池 工作原理及流程 调用本单元,将自动在程序初始化时生成线程池TThreadPoolDown实例 Share_ThreadPool_FTPDown 一、外部调用方法 外部只需要一次性调用 FtpDown() 函数向线程池加入下载任务,下载任务执行中的事件会通过调用时注册的 回调函数 AFtpDownEvent 进行通知。 二、内部工作流程 1、FtpDown()函数将调用TThreadPoolDown.AddFtpDown() ,然后调用TADownFileObj.MakeGetFileSizeObj()分配线程任务 本过程中,将向回调函数 AFtpDownEvent 触发 HEM_ADDURL 事件通知 2、工作线程调用任务对象TFTPHeadObj.DoThreadExecute 过程获取远程文件大小 备注:该功能实际使用到FTP命令SIZE,该命令一些老版本FTP服务器有可能不支持 本过程中,若获取文件大小成功,将向回调函数 AFtpDownEvent 触发 HEM_GETSIZE 事件通知, 若失败,则触发 HEM_ERROR 事件通知 3、得到远程文件大小后,调用TADownFileObj.MakeGetObjs(),分配获取远程文件线程任务 本过程中,开始时,将向回调函数 AFtpDownEvent 触发 HEM_WORKBEGIN 事件通知 在接收数据时,向回调函数 AFtpDownEvent 触发 HEM_WORK 事件通知 4、工作线程调用任务对象 TFTPGetObj.DoThreadExecute 实际下载远程文件数据块 每一个数据块下载任务完成后,触发 HEM_BLOCKOK 事件通知 5、所有数据块完成后,将调用 DoDownloadOK 函数,触发 HEM_DOWNOK 事件通知
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值