前言:你学过的每一样东西,你遭受的每一次苦难,都会在你一生中的某个时候派上用场。—— 菲茨杰拉德

先来看看软件运行效果:

多线程示例1多线程示例3多线程示例4多线程示例2

一、我们先来谈谈什么是多线程下载

下载文件这个活动,通常我们会理解为像是我们在写作文,拿着笔从头写到结尾,当然这可以抽象成单线程下载的情况,而当这篇文章可以好多个人完成的时候,那我们可以分配这些人一人写一段,最终也写成一篇文章,当作文字数一定时,不难想象当然是多个人一起写更快一些。

因此我们可以知道多线程下载的原理就是:通常服务器同时与多个用户连接时,每个用户之间共享带宽。如果N个用户优先级相同那么每个用户连接到服务器上的实际带宽就是1/N,但是当我们启用多线程时我们可以得到的带宽就可以是x/N(x=1,2,3…..),当然这时下载的速度也就越快。

二、实现结构

1.多线程管理类(MultiThreadManager),它负责分配每个线程该从哪里下载,以及得到下载进度。

2.下载线程类(DownThreads),它负责实际的下载操作。

3.图形界面显示类(ViewInit),它负责显示图像界面。

4.监听器(OnClickListener),它负责监听按钮事件。

三、核心内容

实现多线程下载的基础核心是在写入文件时,可以从任意位置写起,即我们使用RandomAccessFile来配合多线程来实现任意位置读写文件,我们可以这样理解:

这是普通流来写入文件时从头开始写入的过程

多线程示例5

这是使用RandomAccessFile任意位置写入文件的过程:

多线程示例6

这样也就可以配合多线程从文件的多个位置下载并写入文件。

四、具体实现:

1、MultiThreadManager

public class MultiThreadManager {
	
	private String urlPath;
	private String filePath;
	
	private int threadNum;
	private int fileSize;
	
	private DownThreads[] threads;
	
	public MultiThreadManager(String urlPath,String filePath,int threadNum)
	{
		this.urlPath=urlPath;
		this.filePath=filePath;
		this.threadNum=threadNum;
		threads=new DownThreads[threadNum];
	}
	public void downLoad() throws Exception
	{
		URL url=new URL(urlPath);
		HttpURLConnection connection=(HttpURLConnection)url.openConnection();
		connection.setRequestMethod("GET");
		connection.setRequestProperty("Connection", "Keep-Alive");
		fileSize=connection.getContentLength();
		connection.disconnect();
		//分配每个线程需要在下载的文件块大小
		int currentPartSize=fileSize/threadNum+1;
		//要用RandomAccessFile来写入文件,因为它支持随机访问模式,即程序可以直接跳转至文件的任意位置来读写
		RandomAccessFile raFile=new RandomAccessFile(filePath,"rw");
		//记录要写入文件的大小
		raFile.setLength(fileSize);
		raFile.close();
		for(int i=0;i<threadNum;i++)
		{
			int startPosition=i*currentPartSize;
			RandomAccessFile currentPartFile=new RandomAccessFile(filePath,"rw");
			//将文件记录指针跳转至我们需要分割的位置
			currentPartFile.seek(startPosition);
			threads[i]=new DownThreads(startPosition,currentPartSize,currentPartFile,urlPath);
			threads[i].start();
		}	
	}
	//得到当前下载完成百分比,用于绘制进度条
	public int getCompleteRate()
	{
		int sumSize=0;
		for(int i=0;i<threadNum;i++)
		{
			sumSize+=threads[i].hasDownSize;
		}
		return (int) ((sumSize*1.0/fileSize)*100);
	}

}

2、DownThreads

public class DownThreads extends Thread{
	//下载线程
	public int hasDownSize;
	private int startPosition;
	private int currentPartSize;
	
	private RandomAccessFile currentPartFile;
	
	private String urlPath;

	public DownThreads(int startPosition,int currentPartSize,RandomAccessFile currentPartFile,String urlPath)
	{
		this.startPosition=startPosition;
		this.currentPartSize=currentPartSize;
		this.currentPartFile=currentPartFile;
		this.urlPath=urlPath;
	}
	public void run()
	{
		try {
			URL url=new URL(urlPath);
			HttpURLConnection connection=(HttpURLConnection)url.openConnection();
			connection.setRequestMethod("GET");
			InputStream in=connection.getInputStream();
			in.skip(this.startPosition);
			//设置缓冲区大小为4是为了提高运行速度
			byte[] buffer=new byte[4];
			int hasRead;
			while(hasDownSize<currentPartSize&&((hasRead=in.read(buffer))!=-1))
			{
				//写入文件操作
				currentPartFile.write(buffer, 0, hasRead);
				hasDownSize+=hasRead;
			}
			currentPartFile.close();
			in.close();
		   } catch (IOException e) {
			e.printStackTrace();
	       }
			
	}
}

3、ViewInit

public class ViewInit extends JFrame{  
    JTextField urlInfoText;  
    JTextField filePathText;  
    JTextField threadNumInfoText;  
    JButton button;  
    MyCommandListener listener;  
    public ViewInit(){  
        init();  
        setVisible(true);  
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
    }  
    public void init()  
    {  
        setLayout(new FlowLayout());  
        setBounds(300,300,700,150);  
        urlInfoText=new JTextField(50);  
        filePathText=new JTextField(50);  
        threadNumInfoText=new JTextField(10);  
        button=new JButton("确定");  
        add(new JLabel("网址:"));  
        add(urlInfoText);  
        add(new JLabel("储存路径:"));  
        add(filePathText);  
        add(new JLabel("想要启动线程数:"));  
        add(threadNumInfoText);  
        add(button);      
    }  
      
    public void setListener(MyCommandListener listener)  
    {  
        this.listener=listener;  
        listener.setFilePath(filePathText);  
        listener.setUrlInfo(urlInfoText);  
        listener.setThreadNum(threadNumInfoText);  
        button.addActionListener(listener);  
    }  
  
}  

4、MyCommandListener接口

public interface MyCommandListener extends ActionListener{  
    //设置资源路径,文件保存路径,线程数量  
    public void setUrlInfo(JTextField urlText);  
    public void setFilePath(JTextField fileText);  
    public void setThreadNum(JTextField threadNum);  
  
} 

5、OnClickListener

public class OnClickListener implements MyCommandListener{

	String urlPath=null;
	String filePath=null;
	int threadNum;
	JTextField urlText;
	JTextField filePathText;
	JTextField threadNumInfo;
	public void setUrlInfo(JTextField urlText)
	{
		this.urlText=urlText;
	}
	public void setFilePath(JTextField fileText)
	{
		this.filePathText=fileText;
	}
	public void setThreadNum(JTextField threadNum)
	{
		this.threadNumInfo=threadNum;
	}
	@Override
	public void actionPerformed(ActionEvent e) {
		urlPath=urlText.getText();
		filePath=filePathText.getText();
		//获取所需线程数
		threadNum=Integer.parseInt(threadNumInfo.getText());
		//开启多线程下载
		MultiThreadManager mtDown=new MultiThreadManager(urlPath,filePath,threadNum);
		try {
			mtDown.downLoad();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		//创建进度对话框
		final ProgressMonitor dialog=new ProgressMonitor(null,"等待任务完成","已完成:",0,100);
		//在新线程中处理耗时任务
		new Thread(()->
		{
			//绘制进度条
			while(mtDown.getCompleteRate()<101)
			{
				dialog.setProgress(mtDown.getCompleteRate());
				if(dialog.isCanceled())
				{
					
					break;
				}
				try{
					Thread.sleep(300);
				}catch(Exception ex)
				{
					ex.printStackTrace();
				}
			}
		}).start();
	}

}

如果你喜欢我的文章请收藏我的个人网站:http://www.bubblyyi.com

点击下载DownLoadTools源码

 

 

打赏

2 Comments

  • Appreciating the commitment you put into your website and detailed information you provide. It’s good to come across a blog every once in a while that isn’t the same unwanted rehashed material. Fantastic read! I’ve saved your site and I’m including your RSS feeds to my Google account.

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注