在后台项目中经常会要求有下载和上传功能的实现,在大文件传输的过程中可以实现断点传输避免重复下载:现在我们来整理一下,也可以作为一个项目的亮点。
由于是本地测试,所以是将"D:/test/remote/file.txt"传送到"D:/test/local/file.txt",如果是使用FTP传送,使用FtpClient就可以了。
1 单线程读取
为方便整理逻辑,先使用单线程完成对需求的实现。
关键: RandomAccessFile 可以实现任意位置开始读取/写入
File f=new File(“D:/test.txt”);
RandomAccessFile localFile = new RandomAccessFile(f,“rw”);
localFile.seek(position);
localFile.write(buf);
code:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import com.springboot.SpringBootHello.service.FtpDownLoadService;
import com.springboot.SpringBootUtill.downLoadThread;
public class FtpDownLoadServiceImply implements FtpDownLoadService{
@Override
public void downLoadSingleFile(File loacalFile, String remoteFilePath) {
// TODO Auto-generated method stub
File remoteFile=new File(remoteFilePath);
checkFile(loacalFile); //查看本地文件是否存在不存在则创建
long localSize=loacalFile.length();
long remoteSize=remoteFile.length();
long step=localSize;//本地文件的大小作为标记的position;
if (localSize<remoteSize && localSize>=0) {
continueDownLoad( loacalFile, remoteFile,step);
}
else {
System.err.println("本地文件已存在");
}
}
@Override
public void downLoadFiles() {
// TODO Auto-generated method stub
}
@Override
public void continueDownLoad(File loacalFile, File remoteFile,
long startIndex) {
// TODO Auto-generated method stub
RandomAccessFile remoteFile2 = null; // RandomAccessFile为任意位置起读取文件
FileOutputStream localFile2 =null;
try {
remoteFile2 = new RandomAccessFile(remoteFile, "rw");
localFile2 = new FileOutputStream(loacalFile, true); // true表示在文末添加
//RandomAccessFile localFile2 = new RandomAccessFile(loacalFile, "rw");
remoteFile2.seek(startIndex);//输入起始坐标
//localFile2.seek(startIndex);
// 数据缓冲区
byte[] buf = new byte[1];
while (remoteFile2.read(buf) != -1) {
localFile2.write(buf);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
//最后关闭IO流
try {
localFile2.close();
remoteFile2.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
@Override
public void checkFile(File filename) {
// TODO Auto-generated method stub
if (!filename.exists()) {
try {
filename.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) {
FtpDownLoadServiceImply tt = new FtpDownLoadServiceImply();
File localFile = new File("D:/test/local/file.txt");
String remote = "D:/test/remote/file.txt";
tt.downLoadSingleFile(localFile, remote);
}
}
2 多线程读取
上述代码已经实现了单线程的下载,我们只需要将单线程改为多线程就可以实现大文件分片下载。
关键:
将需要下载的部分进行划分 long partSize=(remoteSize-localSize)/threadNum;
设置每个线程所需要的标记位 long startIndex = localSize+partSize*(i-1);
long endIndex = localSize+partSize*i;
code:
下载方法:
private void threadDownload(File localFile,File remoteFile, long localSize,long remoteSize) {
int threadNum = 4;
long partSize=(remoteSize-localSize)/threadNum;
for (int i = 1; i <=threadNum; i++) {
long startIndex = localSize+partSize*(i-1);
long endIndex = localSize+partSize*i;
if (i==threadNum) {
endIndex=remoteSize; //设置最后一个线程的终点位置
}
downLoadThread myThread = new downLoadThread(localFile,remoteFile,startIndex,endIndex,i);
Thread thread = new Thread(myThread);
//启动线程
thread.start();
}
}
线程类:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class downLoadThread implements Runnable{
private long startIndex;
private long endIndex;
private int i;
private File remoteFile;
private File loacalFile;
public downLoadThread(File loacalFile, File remoteFile, long startIndex,
long endIndex, int i ) {
// TODO Auto-generated constructor stub
this.remoteFile = remoteFile;
this.loacalFile = loacalFile;
this.startIndex = startIndex;
this.endIndex = endIndex;
this.i = i;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.err.println("我是县城"+i+" endIndex"+endIndex+"startIndex"+startIndex);
RandomAccessFile remoteFile2 =null;
RandomAccessFile localFile2 =null;
try {
remoteFile2 = new RandomAccessFile(remoteFile, "rw");
//FileOutputStream 也可以在文末写入
// FileOutputStream localFile2 = new FileOutputStream(loacalFile, true);
localFile2 = new RandomAccessFile(loacalFile, "rw");
remoteFile2.seek(startIndex);
localFile2.seek(startIndex);
int part= (int)(endIndex-startIndex);
// 数据缓冲区
byte[] buf = new byte[1];
int hasRead=0;
int num= 0;
while ((hasRead=remoteFile2.read(buf)) != -1) {
num=hasRead+num;
//线程终止,继续写入的话会重复写入
if (num>part) {
break;
}
localFile2.write(buf);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
try {
remoteFile2.close();
localFile2.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
我们来温习下多线程的实现方式
详情点击
1 继承 thread
public class myThread extends Thread{
@override
run{
system.out.println("你好我是线程");
}
}
启动线程
myThread mt = new myThread ();
mt.start();
2 实现接口Runnable
public class myThread implment Runnable{
@override
run{
system.out.println("你好我是线程");
}
}
启动线程
myThread mt = new myThread ();
Thread tt = new Thread (mt);
tt.start();
3 实现接口callable
4 使用线程池