代码:

import io.github.viscent.mtia.util.Debug;
import io.github.viscent.mtia.util.Tools;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;

import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;

public class FileDownloaderApp {

public static void main(String[] args) {
String[]pics =new String[]{"https://dlqn.aoscdn.com/beecut-setup-pin.exe",
"https://scpic.chinaz.net/files/pic/pic9/202112/apic37306.jpg"};
Thread downloaderThread = null;
for (String url : pics) {
// 创建文件下载器线程
downloaderThread = new Thread(new FileDownloader(url));
// 启动文件下载器线程
downloaderThread.start();
}
}

// 文件下载器线程类,拓展runnable接口
static class FileDownloader implements Runnable {
private final String fileURL;

public FileDownloader(String fileURL) {
this.fileURL = fileURL;
}
//在run方法中获取文件夹路径,调用下载文件的方法进行下载
@Override
public void run() {
Debug.info("Downloading from " + fileURL);
String fileBaseName = fileURL.substring(fileURL.lastIndexOf('/') + 1);
try {
URL url = new URL(fileURL);
String localFileName = System.getProperty("java.io.tmpdir")//自己电脑系统的临时文件夹路径位置

+ fileBaseName;
Debug.info("Saving to: " + localFileName);
downloadFile(url, new FileOutputStream(
localFileName), 1024);
} catch (Exception e) {
e.printStackTrace();
}
Debug.info("Done downloading from " + fileURL);
}

// 从指定的URL下载文件,并将其保存到指定的输出流中,在这里使用了nio的 ReadableByteChannel和WritableByteChannel 用方法 buf.flip();
// outChannel.write(buf);从给定的缓冲区将字节序列写入此通道
private void downloadFile(URL url, OutputStream outputStream, int bufSize)
throws MalformedURLException, IOException {
// 建立HTTP连接
final HttpURLConnection httpConn = (HttpURLConnection) url
.openConnection();
httpConn.setRequestMethod("GET");
ReadableByteChannel inChannel = null;
WritableByteChannel outChannel = null;
try {
// 获取HTTP响应码
int responseCode = httpConn.getResponseCode();
// HTTP响应非正常:响应码不为2开头
if (2 != responseCode / 100) {
throw new IOException("Error: HTTP " + responseCode);
}

if (0 == httpConn.getContentLength()) {
Debug.info("Nothing to be downloaded " + fileURL);
return;
}
inChannel = Channels
.newChannel(new BufferedInputStream(httpConn.getInputStream()));
outChannel = Channels
.newChannel(new BufferedOutputStream(outputStream));
ByteBuffer buf = ByteBuffer.allocate(bufSize);
while (-1 != inChannel.read(buf)) {
buf.flip();
outChannel.write(buf);
buf.clear();
}
} finally {
// 关闭指定的Channel以及HttpURLConnection
Tools.silentClose(inChannel, outChannel);
httpConn.disconnect();
}
}// downloadFile结束
}// FileDownloader结束
}

其中Debug为自定义的工具类

import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Debug {
private static ThreadLocal<SimpleDateFormat> sdfWrapper = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
}

};

enum Label {
INFO("INFO"),
ERR("ERROR");
String name;

Label(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

// public static void info(String message) {
// printf(Label.INFO, "%s", message);
// }

public static void info(String format, Object... args) {
printf(Label.INFO, format, args);
}

public static void info(boolean message) {
info("%s", message);
}

public static void info(int message) {
info("%d", message);
}

public static void error(String message, Object... args) {
printf(Label.ERR, message, args);
}

public static void printf(Label label, String format, Object... args) {
SimpleDateFormat sdf = sdfWrapper.get();
@SuppressWarnings("resource")
final PrintStream ps = label == Label.INFO ? System.out : System.err;
ps.printf('[' + sdf.format(new Date()) + "][" + label.getName()
+ "]["
+ Thread.currentThread().getName() + "]:" + format + " %n", args);
}
}

其中tools关闭文件的方法:

public final class Tools {
private static final Random rnd = new Random();
private static final Logger LOGGER = Logger.getAnonymousLogger();

public static void startAndWaitTerminated(Thread... threads)
throws InterruptedException {
if (null == threads) {
throw new IllegalArgumentException("threads is null!");
}
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
t.join();
}
}

public static void startAndWaitTerminated(Iterable<Thread> threads)
throws InterruptedException {
if (null == threads) {
throw new IllegalArgumentException("threads is null!");
}
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
t.join();
}
}

public static void randomPause(int maxPauseTime) {
int sleepTime = rnd.nextInt(maxPauseTime);
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

public static void randomPause(int maxPauseTime, int minPauseTime) {
int sleepTime = maxPauseTime == minPauseTime ? minPauseTime : rnd
.nextInt(maxPauseTime - minPauseTime) + minPauseTime;
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

public static void silentClose(Closeable... closeable) {
if (null == closeable) {
return;
}
for (Closeable c : closeable) {
if (null == c) {
continue;
}
try {
c.close();
} catch (Exception ignored) {
}
}
}

public static void split(String str, String[] result, char delimeter) {
int partsCount = result.length;
int posOfDelimeter;
int fromIndex = 0;
String recordField;
int i = 0;
while (i < partsCount) {
posOfDelimeter = str.indexOf(delimeter, fromIndex);
if (-1 == posOfDelimeter) {
recordField = str.substring(fromIndex);
result[i] = recordField;
break;
}
recordField = str.substring(fromIndex, posOfDelimeter);
result[i] = recordField;
i++;
fromIndex = posOfDelimeter + 1;
}
}

public static void log(String message) {
LOGGER.log(Level.INFO, message);
}

public static String md5sum(final InputStream in) throws NoSuchAlgorithmException, IOException {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] buf = new byte[1024];
try (DigestInputStream dis = new DigestInputStream(in, md)) {
while (-1 != dis.read(buf)){}
;
}
byte[] digest = md.digest();
BigInteger bigInt = new BigInteger(1, digest);
String checkSum = bigInt.toString(16);

while (checkSum.length() < 32) {
checkSum = "0" + checkSum;
}
return checkSum;
}

public static String md5sum(final File file) throws NoSuchAlgorithmException, IOException {
return md5sum(new BufferedInputStream(new FileInputStream(file)));
}

public static void delayedAction(String prompt, Runnable action, int delay/* seconds */) {
Debug.info("%s in %d seconds.", prompt, delay);
try {
Thread.sleep(delay * 1000);
} catch (InterruptedException ignored) {
}
action.run();
}

public static Object newInstanceOf(String className) throws InstantiationException,
IllegalAccessException, ClassNotFoundException {
return Class.forName(className).newInstance();
}

}

结果:

可以看到这里开了两个线程用于下载上面的两个链接

使用nio多线程下载网络文件实例_开发语言

文件夹下面可以看到刚刚自己下载好的两个文件:

使用nio多线程下载网络文件实例_java_02

打开图片也可以看到完整的图片显示:

使用nio多线程下载网络文件实例_java_03