首页 > 编程语言 >Java实现大文件多线程下载 提速30倍(文章非全原创 感谢支持)

Java实现大文件多线程下载 提速30倍(文章非全原创 感谢支持)

时间:2023-02-24 11:59:06浏览次数:54  
标签:Java String stringBuilder 30 long new 多线程 public append

目录

一,速度对比

单线程下载,耗时160s

多线程下载,耗时54s

二,HTTP协议Range请求头

Range主要是针对只需要获取部分资源的范围请求,通过指定Range即可告知服务器资源的指定范围,格式:Range: bytes=start-end

比如:获取字节范围 5001-10000

Range: bytes=5001-10000

也可以指定开始位置不指定结束位置,表示获取开始位置之后的全部数据

Range: bytes=5001-

服务器接收到带有Range的请求,会在处理请求后的返回状态码为206 Partial Content的响应。

基于Range的特性,我们就可以实现文件的多线程下载,文件的断点续传。

三,准备工作

1,RestTemplate绕过SSL验证

本文我们使用的是springmvc的RestTemplate,由于链接是Https,所以我们需要设置RestTemplate绕过证书验证。

public class RestTemplateBuilder {
    public static RestTemplateBuilder builder(){
        return new RestTemplateBuilder();
    }

    public RestTemplate build() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
        SSL factory = new SSL();
        factory.setReadTimeout(5000);
        factory.setConnectTimeout(15000);
        return new RestTemplate(factory);
    }

    public static class SSL extends SimpleClientHttpRequestFactory {
        @Override
        protected void prepareConnection(HttpURLConnection connection, String httpMethod)
                throws IOException {
            if (connection instanceof HttpsURLConnection) {
                prepareHttpsConnection((HttpsURLConnection) connection);
            }
            super.prepareConnection(connection, httpMethod);
        }

        private void prepareHttpsConnection(HttpsURLConnection connection) {
            connection.setHostnameVerifier(new SkipHostnameVerifier());
            try {
                connection.setSSLSocketFactory(createSslSocketFactory());
            }
            catch (Exception ex) {
                // Ignore
            }
        }

        private SSLSocketFactory createSslSocketFactory() throws Exception {
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, new TrustManager[] { new SkipX509TrustManager() },
                    new SecureRandom());
            return context.getSocketFactory();
        }

        private class SkipHostnameVerifier implements HostnameVerifier {

            @Override
            public boolean verify(String s, SSLSession sslSession) {
                return true;
            }
        }

        private static class SkipX509TrustManager implements X509TrustManager {

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }

            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) {
            }
        }
    }
}

2,定义DisplayDownloadSpeed下载速度接口

在下载的过程中,我们需要知道当前下载的速度是多少,所以需要定义一个显示下载速度的接口

public interface DisplayDownloadSpeed {
    /**
     * 显示下载速度
     */
    void displaySpeed(String task,long contentLength);
}

3,了解ResponseExtractor接口

因为计算下载速度,我们需要知道每秒传输的字节数是多少,为了监控传输数据的过程,我们需要了解SpringMVC中的接口ResponseExtractor

@FunctionalInterface
public interface ResponseExtractor<T> {

	/**
	 * Extract data from the given {@code ClientHttpResponse} and return it.
	 * @param response the HTTP response
	 * @return the extracted data
	 * @throws IOException in case of I/O errors
	 */
	@Nullable
	T extractData(ClientHttpResponse response) throws IOException;

}

该接口只有一个方法,当客户端和服务端连接建立之后,会调用这个方法,我们可以在这个方法中监控下载的速度。

4,DisplayDownloadSpeed接口的抽象实现AbstractDisplayDownloadSpeedResponseExtractor

public abstract class AbstractDisplayDownloadSpeedResponseExtractor<T> implements ResponseExtractor<T>,DisplayDownloadSpeed {
    /**
     * 显示下载速度
     */
    @Override
    public void displaySpeed(String task, long contentLength) {
        long totalSize = contentLength / 1024;
        CompletableFuture.runAsync(()->{
            long temp = 0;
            long speed;
            StringBuilder stringBuilder = new StringBuilder();
            while (contentLength - temp > 0){
                speed = getAlreadyDownloadLength() - temp;
                temp = getAlreadyDownloadLength();
                stringBuilder.append("\r");//不换行进行覆盖
                stringBuilder.append(task + " 文件总大小: " + totalSize + " KB,已下载:" + (temp / 1024) + "KB,下载速度:"+ (speed/1000)+"KB/S");
                System.out.print(stringBuilder.toString());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }

    @Override
    public T extractData(ClientHttpResponse clientHttpResponse) throws IOException {
        long contentLength = clientHttpResponse.getHeaders().getContentLength();
        this.displaySpeed(Thread.currentThread().getName(),contentLength);
        return this.doExtractData(clientHttpResponse);
    }

    public abstract T doExtractData(ClientHttpResponse clientHttpResponse) throws IOException;

    /**
     * 获取已经下载了多少字节
     */
    protected abstract long getAlreadyDownloadLength();
}

四,简单的文件下载器

这里使用的RestTemplate调用execute,先文件获取到字节数组,再将字节数组直接写到目标文件。

这里我们需要注意的点是:这种方式会将文件的字节数组全部放入内存中,及其消耗资源,我们来看看如何实现。

1,创建ByteArrayResponseExtractor类继承AbstractDisplayDownloadSpeedResponseExtractor

/**
 * 这种方式会将文件的字节数组全部放入内存中,及其消耗资源,只适用于小文件的下载,如果下载几个G的文件,内存肯定是不够用的
 */
@Deprecated
public class ByteArrayResponseExtractor extends AbstractDisplayDownloadSpeedResponseExtractor<byte[]> {
    //保存已经下载的字节数
    private long byteCount;

    @Override
    public byte[] doExtractData(ClientHttpResponse clientHttpResponse) throws IOException {
        long contentLength = clientHttpResponse.getHeaders().getContentLength();
        ByteArrayOutputStream out = new ByteArrayOutputStream(contentLength >= 0 ? (int) contentLength : StreamUtils.BUFFER_SIZE);
        InputStream in = clientHttpResponse.getBody();
        int byteRead;
        for (byte[] buffer = new byte[4096]; (byteRead = in.read(buffer)) != -1; byteCount += byteRead) {
            out.write(buffer,0,byteRead);
        }
        out.flush();
        return out.toByteArray();
    }

    @Override
    protected long getAlreadyDownloadLength() {
        return byteCount;
    }
}

2,调用RestTemplate.execute()执行下载,保存字节数据到文件中

/**
     * 单线程基于内存下载
     *
     * @param fileURL
     * @param filePath
     * @throws NoSuchAlgorithmException
     * @throws KeyStoreException
     * @throws KeyManagementException
     */
    public void downloadToMemory(String fileURL, String filePath) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
        long start = System.currentTimeMillis();
        RestTemplate restTemplate = RestTemplateBuilder.builder().build();

        byte[] bytes = restTemplate.execute(fileURL, HttpMethod.GET, null, new ByteArrayResponseExtractor());
        try {
            Files.write(Paths.get(filePath), Objects.requireNonNull(bytes));
            System.out.println("总共文件下载耗时:" + (System.currentTimeMillis() - start) / 1000 + "s");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

测试下载

public static void main(String[] args) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException, InterruptedException {
        FileDownloader fileDownloader = new FileDownloader();
        String fileURL = "https://mirrors.tuna.tsinghua.edu.cn/eclipse/4diac/releases/2.0/4diac-ide/4diac-ide_2.0.0-linux.gtk.x86_64.tar.gz";
        fileDownloader.downloadToMemory(fileURL,"C:\\Users\\fanto\\Downloads\test.tar.gz");
    }

执行一段时间后,我们可以看到内存已经使用了800M左右,所以这种方式会将文件的字节数组全部放入内存中,及其消耗资源,只适用于小文件的下载,如果下载几个G的文件,内存肯定是不够用的。

五,单线程大文件下载

上面的方式只能下载小的文件,那大文件的下载该用什么方式呢?我们可以把流输出到文件而不是内存中,接下来我们实现大文件的下载。

1,创建FileResponseExtractor类继承AbstractDisplayDownloadSpeedResponseExtractor

把流输出到文件中

public class FileResponseExtractor extends AbstractDisplayDownloadSpeedResponseExtractor<File> {
    //已下载的字节数
    private long byteCount;
    //文件的路径
    private String filePath;

    public FileResponseExtractor(String filePath) {
        this.filePath = filePath;
    }

    @Override
    public File doExtractData(ClientHttpResponse clientHttpResponse) throws IOException {
        long contentLength = clientHttpResponse.getHeaders().getContentLength();
        InputStream in = clientHttpResponse.getBody();
        File file = new File(filePath);
        FileOutputStream out = new FileOutputStream(file);
        int byteRead;
        for (byte[] buffer = new byte[4096]; (byteRead = in.read(buffer)) != -1; byteCount += byteRead) {
            out.write(buffer,0,byteRead);
        }
        out.flush();
        out.close();
        return file;
    }

    @Override
    protected long getAlreadyDownloadLength() {
        return byteCount;
    }
}

2,文件下载器,先把流输出到临时文件(xxxx.download),下载完成再重命名文件

/**
     * 单线程基于文件下载
     *
     * @param fileURL
     * @param filePath
     * @throws NoSuchAlgorithmException
     * @throws KeyStoreException
     * @throws KeyManagementException
     */
    public void downloadToFile(String fileURL, String filePath) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
        long start = System.currentTimeMillis();
        RestTemplate restTemplate = RestTemplateBuilder.builder().build();
        FileResponseExtractor fileResponseExtractor = new FileResponseExtractor(filePath + ".download");
        File tempFile = restTemplate.execute(fileURL, HttpMethod.GET, null, fileResponseExtractor);
        tempFile.renameTo(new File(filePath));
        System.out.println("总共文件下载耗时:" + (System.currentTimeMillis() - start) / 1000 + "s");
    }

测试下载

  public static void main(String[] args) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException, InterruptedException {
        FileDownloader fileDownloader = new FileDownloader();
        String fileURL = "https://mirrors.tuna.tsinghua.edu.cn/eclipse/4diac/releases/2.0/4diac-ide/4diac-ide_2.0.0-linux.gtk.x86_64.tar.gz";
        fileDownloader.downloadToFile(fileURL, "C:\\Users\\fanto\\Downloads\\test.tar.gz");
    }

执行一段时间后,我们再看看内存的使用情况,发现这种方式内存消耗较少,效果比较理想。

六,多线程大文件下载

思路

  1. 先请求文件,返回文件大小
  2. 根据文件大小,给多个线程分配要请求的部分文件大小(均匀分配,最后一个线程或多或少)
  3. 开启多线程,每个线程去调用上面单线程的逻辑,请求头Range指定文件大小的范围

1,提供多线程下载的方法downloadToFileMultiThread()

public void downloadToFileMultiThread(String fileURL, String filePath, int threadNumber) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException, InterruptedException {
        long start = System.currentTimeMillis();
        //先请求文件,得到文件总大小
        URL url = new URL(fileURL);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(10000);
        conn.setRequestMethod("GET");
        // 得到需要下载的文件大小
        long fileLength = conn.getContentLengthLong();
        conn.disconnect();

        /*
         *  计算每条线程下载的字节数,以及每条线程起始下载位置与结束的下载位置,
         *  因为不一定平均分,所以最后一条线程下载剩余的字节
         *  然后创建线程任务并启动
         *  Main线程等待每条线程结束(join()方法)
         */
        long oneThreadReadByteLength = fileLength / threadNumber;
        CountDownLatch countDownLatch = new CountDownLatch(threadNumber);
        for (int i = 0; i < threadNumber; i++) {
            long startPosition = i * oneThreadReadByteLength;
            long endPosition = i == threadNumber - 1 ? fileLength : (i + 1) * oneThreadReadByteLength - 1;
            Thread t = new Thread(new Task(startPosition, endPosition, countDownLatch, filePath, fileURL, "download" + i));
            t.start();
        }
        countDownLatch.await();
        mergeTempFiles(filePath, threadNumber);
        System.out.println("总共文件下载耗时:" + (System.currentTimeMillis() - start) / 1000 + "s");
    }

    public void mergeTempFiles(String filePath, int threadNumber) throws IOException, InterruptedException {
        System.out.println("开始合并");
        //开始合并
        OutputStream os = new BufferedOutputStream(new FileOutputStream(filePath));
        //对临时目录的所有文件分片进行遍历,进行合并
        for (int i = 0; i < threadNumber; i++) {
            File tempFile = new File(filePath + ".download" + String.valueOf(i));
            System.out.println("文件名称:" + tempFile.getAbsolutePath());
            while (!tempFile.exists()) {
                Thread.sleep(100);
            }
            byte[] bytes = FileUtils.readFileToByteArray(tempFile);
            os.write(bytes);
            os.flush();
            tempFile.delete();
        }
        os.close();
    }

2,线程类

class Task implements Runnable {
        private long startPosition;
        private long endPosition;
        private CountDownLatch countDownLatch;
//        private RestTemplate restTemplate;
        private String filePath;
        private String fileURL;
        private String tempName;

        Task(long startPosition,
             long endPosition,
             CountDownLatch countDownLatch,
             String filePath,
             String fileURL,
             String tempName) {
            this.startPosition = startPosition;
            this.endPosition = endPosition;
            this.countDownLatch = countDownLatch;
            this.filePath = filePath;
            this.fileURL = fileURL;
            this.tempName = tempName;
        }

        @Override
        public void run() {
            try {
                RestTemplate restTemplate = RestTemplateBuilder.builder().build();
                FileResponseExtractor fileResponseExtractor = new FileResponseExtractor(filePath + "." + tempName);
                // 借助拦截器的方式来实现塞统一的请求头
                ClientHttpRequestInterceptor interceptor = (httpRequest, bytes, execution) -> {
                    httpRequest.getHeaders().set("Range", "bytes=" + startPosition + "-" + endPosition);
                    return execution.execute(httpRequest, bytes);
                };
                restTemplate.getInterceptors().add(interceptor);
                File tempFile = restTemplate.execute(fileURL, HttpMethod.GET, null, fileResponseExtractor);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            } catch (KeyStoreException e) {
                throw new RuntimeException(e);
            } catch (KeyManagementException e) {
                throw new RuntimeException(e);
            } finally {
                countDownLatch.countDown();
            }
        }
    }

3,下载测试

public static void main(String[] args) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException, InterruptedException {
        FileDownloader fileDownloader = new FileDownloader();
        String fileURL = "https://mirrors.tuna.tsinghua.edu.cn/eclipse/4diac/releases/2.0/4diac-ide/4diac-ide_2.0.0-linux.gtk.x86_64.tar.gz";
        fileDownloader.downloadToFileMultiThread(fileURL, "C:\\Users\\fanto\\Downloads\\test.tar.gz", 10);
    }

七,扩展

1,下载使用进度条展示

进度条工具类

package com.example.demo.web;

import java.io.IOException;

public class PrintProgressBar {
    //总大小
    private long size;
    //必须设置总大小
    public PrintProgressBar(long size) {
        this.size = size;
    }
    //配置
    //是否打印进度条
    private boolean printProgressBar = true;
    //是否打印速度
    private boolean printSpeed = true;
    //是否打印百分比
    private boolean printPercentage = true;
    //是否打印总大小
    private boolean printSize = true;
    //是否开启单位换算
    private boolean byteConversion = true;
    //进度条长度
    private int percentageLength = 100;
    //是否在结束时自动打印信息
    private boolean autoPrintTime = true;
    //减少打印次数
    private boolean print100 = true;
    //单位
    private String conversion = "个";
    public PrintProgressBar setPrintProgressBar(boolean printProgressBar) {
        this.printProgressBar = printProgressBar;
        return this;
    }
    public PrintProgressBar setPrintSpeed(boolean printSpeed) {
        this.printSpeed = printSpeed;
        return this;
    }
    public PrintProgressBar setPrintPercentage(boolean printPercentage) {
        this.printPercentage = printPercentage;
        return this;
    }
    public PrintProgressBar setPrintSize(boolean printSize) {
        this.printSize = printSize;
        return this;
    }
    public PrintProgressBar setByteConversion(boolean byteConversion) {
        this.byteConversion = byteConversion;
        return this;
    }
    public PrintProgressBar setPercentageLength(int percentageLength) {
        this.percentageLength = percentageLength;
        return this;
    }
    public PrintProgressBar setAutoPrintTime(boolean autoPrintTime) {
        this.autoPrintTime = autoPrintTime;
        return this;
    }
    public PrintProgressBar setPrint100(boolean print100) {
        this.print100 = print100;
        return this;
    }
    public PrintProgressBar setConversion(String conversion) {
        this.conversion = conversion;
        return this;
    }
    //时间
    private long timeStart;//最开始的时间
    private long timeEnd;//完全结束的时间
    private double progress;//已完成进度(百分比)
    private long count;//已完成进度(数量)
    //速度
    private long speedStart;//记录每秒速度的开始时间
    //记录单位时间内执行的数据
    private long speedNum;
    //记录速度值, 放在这里是因为不一定每一次打印都要刷新速度, 中间的间隔可以用记录在这里的旧数据
    private long speed;
    //记录完成百分比, 用于减少打印次数
    private int flag;
    //内部计算总完成量
    public void printAppend(long count) {
        this.count += count;
        print(this.count);
    }
    /**
     * 核心方法
     * @param count 当前完成的数量
     */
    public void print(long count) {
        //开始计时
        if (timeStart == 0) timeStart = speedStart = System.currentTimeMillis();
        //如果需要在下载完成后自动打印总耗时和平均速度, 需要每次都进行计算完成度, 当这个值不小于100则代表完成
        if (autoPrintTime) progress = count * 100 / (size + 0.0);
        double percentage = 0;//当前完成百分比
        //获取当前完成百分比
        {
            if (percentageLength != 100) {//自定义进度条长度后要根据进度条长度进行计算
                percentage = count * percentageLength / (size + 0.0);
            } else if (!autoPrintTime) {//默认的进度条长度并且没有开启结束自动打印, 需要在这里计算完成百分比
                percentage = count * 100 / (size + 0.0);
            } else {//默认的进度条长度并且 开启 了结束自动打印, 计算步骤以在上面完成, 无需再次计算
                percentage = progress;
            }
        }
        if (print100 && percentage < flag) {//当前进度还满足打印条件
            return;
        } else {
            flag++;
        }
        //准备打印
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("\r");//不换行进行覆盖
        //打印进度条
        if (printProgressBar){
            stringBuilder.append("[");
            for (int i = 0; i < percentage; i++) {
                stringBuilder.append("=");
            }
            stringBuilder.append(">");
            for (int i = 0; i < percentageLength - percentage; i++) {
                stringBuilder.append(" ");
            }
            stringBuilder.append("]");
        }
        //打印百分比
        if (printPercentage){
            if (percentageLength == 100) {//进度条长度默认100, 不用重新计算
                stringBuilder.append(String.format("%.2f", percentage));
            } else if(autoPrintTime) {//开启了结束后自动打印, 百分比已经计算, 不用重新计算
                stringBuilder.append(String.format("%.2f", progress));
            } else {//自定义了进度条长度并且关闭了结束后自动打印, 打印百分比要计算值
                stringBuilder.append(String.format("%.2f", count * 100 / (size + 0.0)));
            }
            stringBuilder.append("%");
        }
        //打印总大小
        if (printSize) {
            stringBuilder.append(" 总大小: ");
            if (byteConversion) {
                getByteConversion(stringBuilder, size, false);
            } else {
                stringBuilder.append(size);
                stringBuilder.append(conversion);
            }
        }
        //打印速度
        if (printSpeed){
            //获取当前时间
            long speedEnd = System.currentTimeMillis();
            //计算当前时间 减去 上次打印速度的时间
            long time = speedEnd - speedStart;
            if (time >= 1000 || //距离上次打印时间超过1秒才会更新速度数据
                    (time != 0 && speedEnd - timeStart < 1000)) {//或者程序总执行时间还不到1秒也可以计算
                //当前进度减去上次记录的进度, 从毫秒转换到秒
                speed = (count - speedNum) * 1000 / time;
                speedNum = count;//记录这次的进度, 给下次计算速度的时候提供数据
                speedStart = speedEnd;//记录这次的时间, 给下次计算速度的时候提供数据
            }
            stringBuilder.append(" 速度: ");
            //开启单位换算
            {
                if (byteConversion) {//进制转换
                    getByteConversion(stringBuilder, speed, true);
                } else {//不需要进制转换
                    stringBuilder.append(speed);
                    stringBuilder.append(conversion);
                    stringBuilder.append("/s");
                }
            }
        }
        System.out.print(stringBuilder.toString());
        if (autoPrintTime) {
            //完成进度大于等于100则打印总耗时和平均速度
            if (progress >= 100) {
                printTime();
            }
        }
    }
    /**
     * 打印总耗时和平均每秒速度
     */
    public void printTime() {
        //设置结束时间
        if (timeEnd == 0) timeEnd = System.currentTimeMillis();
        //获取总时间
        long time = timeEnd - timeStart;
        //时间转换倍率
        int conversion = 1;
        //打印时间单位
        String timeConversion = "";
        //获取时间单位和转换倍率
        {
            if (time / 1000 >= 60 && time / 1000 < 60 * 60) {//大于等于一分钟, 小于一小时
                conversion = 60;
                timeConversion = "分钟";
            } else if (time / 1000 >= 60 * 60) {//大于等于一小时
                conversion = 60 * 60;
                timeConversion = "小时";
            } else {
                timeConversion = "秒";
            }
        }
        //准备打印
        StringBuilder stringBuilder = new StringBuilder();
        //打印时间
        {
            stringBuilder.append("\n");//刚刚打印完进度条, 需要换行
            stringBuilder.append("总共耗时: ");
            //总毫秒 转换成秒在 除 转换倍率 ---> 保留两位小数点
            stringBuilder.append(String.format("%.2f", (time + 0.0) / conversion / 1000));
            stringBuilder.append(timeConversion);
        }
        //打印平均速度
        {
            //time小于1必然发生 除0 异常
            if (time > 1) {
                stringBuilder.append("\n");
                stringBuilder.append("平均速度: ");
                //总大小 除 总时间(秒)
                double byteConversionCount = size / ((time + 0.0) / 1000);
                if (byteConversion) {//进制转换
                    getByteConversion(stringBuilder, byteConversionCount, true);
                } else {
                    //不进制转换
                    stringBuilder.append(String.format("%.2f", byteConversionCount));
                    stringBuilder.append(this.conversion);
                    stringBuilder.append("/s");
                }
            }
        }
        //打印
        System.out.println(stringBuilder.toString());
    }
    /**
     * 进制转换
     * @param stringBuilder 将转换后的数据放在这个StringBuilder中
     * @param num 需要转换的数据
     * @param printConversion 是否打印 “/s”
     */
    public void getByteConversion(StringBuilder stringBuilder, double num, boolean printConversion) {
        if (num < 1024) {
            stringBuilder.append(String.format("%.2f", num));
            stringBuilder.append("B");
        } else if (num < 1024 * 1024) {
            stringBuilder.append(String.format("%.2f", num / 1024));
            stringBuilder.append("KB");
        } else {
            stringBuilder.append(String.format("%.2f", num / 1024 / 1024));
            stringBuilder.append("MB");
        }
        if (printConversion) stringBuilder.append("/s");
    }
    public PrintProgressBar noPrintProgressBar() {
        printProgressBar = false;
        return this;
    }
    public PrintProgressBar noPrintSpeed() {
        printSpeed = false;
        return this;
    }
    public PrintProgressBar noPrintPercentage() {
        printPercentage = false;
        return this;
    }
    public PrintProgressBar noPrintSize() {
        printSize = false;
        return this;
    }
    public PrintProgressBar noByteConversion() {
        byteConversion = false;
        return this;
    }
    public PrintProgressBar noAutoPrintTime() {
        autoPrintTime = false;
        return this;
    }
    public PrintProgressBar noPrint100() {
        print100 = false;
        return this;
    }

    public static void main(String[] args) throws InterruptedException, IOException {
        long size = 1003;
        //创建对象并且赋值总大小
        PrintProgressBar printProgressBar = new PrintProgressBar(size)
                //自定义配置
//                .noPrintProgressBar()//取消打印进度条
//                .noPrintSpeed()//取消打印速度
//                .noPrintPercentage()//取消打印百分比
//                .noPrintSize()//取消打印总大小
                .noByteConversion()//取消字节转换
                .setPercentageLength(50)//设置进度条长度
//                .noAutoPrintTime()//取消完成后自动打印总耗时和平均每秒速度
//                .noPrint100()//增加打印次数, 实时监控, 对性能有略微影响(在我的渣渣机子上打印20亿次仅影响10秒)
                .setConversion("只")//自定义单位(此配置需要关闭字节转换才有效果)
                ;
        for (long i = 0; i <= size; i++) {
            printProgressBar.print(i);//打印进度条
            Thread.sleep(1);//程序执行太快, 需要睡一会
        }
    }
}

2,运行效果

3,Java控制台打印如何覆盖打印(原行更新)

StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("\r");//不换行进行覆盖
stringBuilder.append(task + " 文件总大小: " + totalSize + " KB,已下载:" + (temp / 1024) + "KB,下载速度:"+ (speed/1000)+"KB/S");
System.out.print(stringBuilder.toString());

通过\r并且打印不换行就可以实现覆盖(原行更新)的效果啦!

文章主要部分转载自:https://www.xiaohongshu.com/explore/632de36c000000001801b9c5

标签:Java,String,stringBuilder,30,long,new,多线程,public,append
From: https://www.cnblogs.com/fantongxue/p/17150777.html

相关文章

  • Java方法
    Java方法1.方法的重载方法名称必须相同,参数列表必须不同(个数不同或类型不同,参数排列顺序不同)publicstaticintmax(doublenum1,doublenum2){}publicstaticintmax......
  • Java流程控制
    Java流程控制1.用户交互Scannernext()不能得到带有空格的字符串,空格后字符串自动去除packagecom.zhang.scanner;importjava.util.Scanner;publicclassDemo01{......
  • 【Java数据结构和算法】002-数据结构和算法概述
    目录​​一、数据结构和算法的关系​​​​二、实际编程中遇到的问题​​​​1、一段Java代码​​​​代码:​​​​问题:​​​​2、一个五子棋程序​​​​图示:​​​​问题......
  • java 注解基础
    java内置注解注解作用描述@Override将覆盖父类中的方法作用在子类的方法上@Deprecated代码被弃用使用了被@Deprecated注解的代码则编译器将发出警告......
  • Java数组学习
    Java数组学习ArrayDemo1packagecom.yuan.array;publicclassArrayDemo1{//变量的类型变量的名字=变量的值;//数组类型//数组的长度......
  • JavaScript加密代码反调试
    JavaScript奇技淫巧:加密JS代码反调试JS代码混淆加密,已被很多人使用,因为它真的很有用、很实用,可以用于保护代码、防护分析、复制、盗用,还可以用于小游戏过审、APP加固等方面......
  • 编写高效的Java代码:常用的优化技巧【四】之并发编程技巧
    ​​编写高效的Java代码:常用的优化技巧【一】​​​​编写高效的Java代码:常用的优化技巧【二】​​​​编写高效的Java代码:常用的优化技巧【三】之JVM调优​​一、使用并发......
  • LeetCode-20. 有效的括号(java)
    一、前言:......
  • Java面试
    为什么重写equals还要重写hashCode方法1、如果equals和hashCode方法的实现不一致,在集合中使用时可能会报错,比如找不到对象、重复存储对象2、比如Set集合存储的......
  • 【Javascript】el-upload 上传图片转 base64 (使用 FileReader)
    e.target.result结果......