参考资料
疯狂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());
}