参考资料
疯狂Java讲义IO操作篇,多线程篇。课后题讲义。
业务背景
1、启动程序时,检测任务列表是否有没有完成的下载任务,有的话,呈现出下载任务
2、有任务的话,继续下载,没有的话建立下载任务。
3、暂停下载,关闭下载任务的所有线程
3、开始下载,唤醒所有的下载任务。
基本步骤
讲义实现的步骤很明确,而且逻辑也很严谨。自己做主张,把讲义的代码改了改,发现不能改啊,改了会有好多麻烦。讲义的代码结构设计很棒。
1、建立远程连接,获取下载文件的大小。
2、根据大小和线程数,循环操作生成块实体。
3、根据块文件实体集合,产生相应的4个块文件,启动4个线程下载文件。
4、等待子线程结束,根据块文件实体集合的begin位置,合成一个文件。
扫描二维码关注公众号,回复:
941736 查看本文章
扩展
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()); }