深度探究java底层原理系列之------利用多线程或者单线程实现对1GB以上内容文件夹的复制(包含知识:递归算法,多线程操作,io流的读取...)

大家好,我是一位在java学习圈中不愿意透露姓名并苟且偷生的小学员,如果文章有错误之处,还望海涵,欢迎多多指正
如果你从本文学到有用的干货知识,那么请您点赞,关注,评论,收藏

实现功能前的知识补充:
1.如何实现线程:
方法一 : 自己描述一个类,在此类未继承其他类的情况下(除了默认继承Object)去继承Thread类并重写run方法,通过new自己描述类的对象r1,r2,r3,让对象调用父类Thread中的start方法让线程进入就绪状态;
为了避免java中单继承的影响,故产生方法二:
方法二 : 自己描述一个类,不用管此类的继承情况,直接实现Runnable接口并重写run方法,通过new自己描述类的对象r1,r2,r3,将对象包裹在Thread中,例:Thread t1 = new Thread(r1); 然后调用Thread中的start方法让线程进入就绪状态;

对多线程与单线程读取文件的效率感兴趣的小伙伴可以看一些计算机组成原理的书
本文不过多解释,仅浅谈二者的区别

以下为单线程(即正常处理无需实现线程,单线程指main,main为用户线程)实现过程:

package mythread;

import java.io.*;

public class TestIOAndThread extends Thread{

    //文件夹或文件的复制
    public void fileFolderCopy(File file , String newPath){
        //拼接新的路径
        String oldFilePath = file.getName();
        //找最后一个(即操作目标)进行拼接
        String newFilePath = newPath + "//" + oldFilePath;
        File newFile = new File(newFilePath);
        //判断当前的File对象是否是一个文件
        File[] files = file.listFiles();
        if(files != null){      //是个文件夹(可能为空可能不为空)
            //创建文件夹
            newFile.mkdir();
            System.out.println(newFile.getName() + "文件夹复制成功");
            //看看文件夹中是否还有元素  有就递归遍历
            if(files.length != 0) {
                for (File f : files) {
                    this.fileFolderCopy(f,newFilePath);
                }
            }
        }else{      //是个文件
            FileInputStream fis = null;
            FileOutputStream fos = null;
            try {
                fis = new FileInputStream(file);
                fos = new FileOutputStream(newFile);
                byte[] a = new byte[1024];
                int count = fis.read(a);
                while(count != -1){
                    fos.write(a,0,count);
                    fos.flush();
                    count = fis.read(a);
                }
                System.out.println(newFile.getName() + "文件复制成功");
            } catch (IOException e) {
                e.printStackTrace();
            } finally{      //分开关保证必须执行 健壮性
                try {
                    if(fis != null) {
                        fis.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if(fos != null) {
                        fos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args){
        //利用skip方法以及单线程同时读取一个视频
        File file = new File("D:\\BaiduNetdiskDownload\\10-MySQL");
        TestIOAndThread tiat = new TestIOAndThread();
        //记录刚开始的时间(毫秒值)
        long time1 = System.currentTimeMillis();
        tiat.fileFolderCopy(file,"E://test");
        //记录结束的时间(毫秒值)
        long time2 = System.currentTimeMillis();
        long time = time2 - time1;
        System.out.println("本次复制共用时约为:"+time/1000+"秒");

    }
}

运行结果:

10-MySQL文件夹复制成功
01 数据库概念文件夹复制成功
2_2_1_1_今日内容(2).avi文件复制成功
2_2_1_2_数据库的基本概念.avi文件复制成功
2_2_1_3_常见的数据库软件介绍.avi文件复制成功
02 MySQL安装与使用文件夹复制成功
2_2_2_4_MySQL的安装.avi文件复制成功
2_2_2_5_MySQL的卸载.avi文件复制成功
2_2_2_6_MySQL服务启动和关闭.avi文件复制成功
2_2_2_7_MySQL登录和退出.avi文件复制成功
2_2_2_8_MySQL目录结构.avi文件复制成功
03 SQL基本概念与通用语法文件夹复制成功
2_2_3_10_SQL通用语法.avi文件复制成功
2_2_3_9_SQL基本概念.avi文件复制成功
04 数据库的CRUD操作文件夹复制成功
2_2_4_11_SQL分类.avi文件复制成功
2_2_4_12_DDL_操作数据库_创建&查询.avi文件复制成功
2_2_4_13_DDL_操作数据库_修改&删除&使用.avi文件复制成功
05 数据库表的CRUD操作文件夹复制成功
2_2_5_14_DDL_操作表_查询.avi文件复制成功
2_2_5_15_DDL_操作表_创建.avi文件复制成功
2_2_5_16_DDL_操作表_删除.avi文件复制成功
2_2_5_17_DDL_操作表_修改.avi文件复制成功
2_2_5_18_图形化界面工具SQLyog.avi文件复制成功
06 数据库表中记录的基本操作文件夹复制成功
2_2_6_19_DML_添加数据.avi文件复制成功
2_2_6_20_DML_删除数据.avi文件复制成功
2_2_6_21_DML_修改数据.avi文件复制成功
07 数据库的查询操作文件夹复制成功
2_2_7_22_DQL_基础查询.avi文件复制成功
2_2_7_23_DQL_条件查询.avi文件复制成功
2_2_7_24_DQL_条件查询_模糊查询.avi文件复制成功
2_2_7_25_今日内容(3).avi文件复制成功
2_2_7_26_DQL_排序查询.avi文件复制成功
2_2_7_27_DQL_聚合函数.avi文件复制成功
2_2_7_28_DQL_分组查询.avi文件复制成功
2_2_7_29_DQL_分页查询.avi文件复制成功
08 表的约束文件夹复制成功
2_2_8_01_约束_概述.avi文件复制成功
2_2_8_02_约束_非空约束.avi文件复制成功
2_2_8_03_约束_唯一约束.avi文件复制成功
2_2_8_04_约束_主键约束.avi文件复制成功
2_2_8_05_约束_主键约束_自动增长.avi文件复制成功
2_2_8_06_约束_外键约束.avi文件复制成功
2_2_8_07_约束_外键约束_级联操作.avi文件复制成功
09 多表操作文件夹复制成功
2_2_9_01_多表关系介绍.avi文件复制成功
2_2_9_02_多表关系_一对多关系实现.avi文件复制成功
2_2_9_03多表关系_多对多关系实现.avi文件复制成功
2_2_9_04_多表关系_一对一关系实现.avi文件复制成功
2_2_9_05_多表关系_案例.avi文件复制成功
10 三大范式文件夹复制成功
2_2_10_01_范式概述.avi文件复制成功
2_2_10_02_三大范式详解.avi文件复制成功
11 数据库的备份和还原文件夹复制成功
2_2_11_01_数据库的备份和还原.avi文件复制成功
12 多表查询文件夹复制成功
2_2_12_1_今日内容(4).avi文件复制成功
2_2_12_2_多表查询_概述.avi文件复制成功
2_2_12_3_多表查询_内连接.avi文件复制成功
2_2_12_4_多表查询_外连接.avi文件复制成功
13 子查询文件夹复制成功
2_2_13_5_多表查询_子查询概述.avi文件复制成功
2_2_13_6_多表查询_子查询情况1&情况2.avi文件复制成功
2_2_13_7_多表查询_子查询情况3.avi文件复制成功
14 多表查询练习文件夹复制成功
2_2_14_10_多表查询_练习3.avi文件复制成功
2_2_14_8_多表查询_练习1.avi文件复制成功
2_2_14_9_多表查询_练习2.avi文件复制成功
15 事务文件夹复制成功
2_2_15_11_事务_基本演示.avi文件复制成功
2_2_15_12_事务_默认自动提交&手动提交.avi文件复制成功
2_2_15_13_事务_事务四大特征.avi文件复制成功
2_2_15_14_事务_事务隔离级别介绍.avi文件复制成功
2_2_15_15_事务_事务隔离级别演示1.avi文件复制成功
2_2_15_16_事务_事务隔离级别演示2.avi文件复制成功
16 用户管理和权限管理文件夹复制成功
2_2_16_17_DCL_管理用户_增删查.avi文件复制成功
2_2_16_18_DCL_管理用户_修改密码.avi文件复制成功
2_2_16_19_DCL_管理权限.avi文件复制成功
本次复制共用时约为:67秒

Process finished with exit code 0

不同的电脑性能不太一样,感兴趣的小伙伴可以在自己编辑器上试试

以下为多线程实现过程(注:此处实现线程采用的是方法一,因为符合方法一的条件,另外此实现过程未做计时处理(因为多线程的原因没办法计时,至少我不会哈哈)有兴趣的小伙伴可以用手机计时)

package mythread;

import java.io.*;

public class TestIOAndThread extends Thread{

    private File file;
    private String newPath;
    private long bytes;

    public TestIOAndThread(File file , String newPath , long bytes){
        this.file = file;
        this.newPath = newPath;
        this.bytes = bytes;
    }

    @Override
    public void run() {
        this.fileFolderCopy(this.file,this.newPath,this.bytes);
    }

    //文件夹或文件的复制
    public void fileFolderCopy(File file , String newPath , long bytes){
        //拼接新的路径
        String oldFilePath = file.getName();
        //找最后一个(即操作目标)进行拼接
        String newFilePath = newPath + "//" + oldFilePath;
        File newFile = new File(newFilePath);
        //判断当前的File对象是否是一个文件
        File[] files = file.listFiles();
        if(files != null){      //是个文件夹(可能为空可能不为空)
            //创建文件夹
            newFile.mkdir();
            System.out.println(newFile.getName() + "文件夹复制成功");
            //看看文件夹中是否还有元素  有就递归遍历
            if(files.length != 0) {
                for (File f : files) {
                    this.fileFolderCopy(f,newFilePath,bytes);
                }
            }
        }else{      //是个文件
            FileInputStream fis = null;
            FileOutputStream fos = null;
            try {
                fis = new FileInputStream(file);
                fos = new FileOutputStream(newFile);
                byte[] a = new byte[1024];
                //跳过bytes字节后再读取 读取过的内容不会再读了
                fis.skip(bytes);
                int count = fis.read(a);
                while(count != -1){
                    fos.write(a,0,count);
                    fos.flush();
                    count = fis.read(a);
                }
                System.out.println(newFile.getName() + "文件复制成功");
            } catch (IOException e) {
                e.printStackTrace();
            } finally{      //分开关保证必须执行 健壮性
                try {
                    if(fis != null) {
                        fis.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if(fos != null) {
                        fos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    @SuppressWarnings("all")
    public static void main(String[] args){
        //利用skip方法以及多线程同时读取一个视频
        File file = new File("D:\\BaiduNetdiskDownload\\10-MySQL");
        TestIOAndThread tiat1 = new TestIOAndThread(file,"E://test",0);
        TestIOAndThread tiat2 = new TestIOAndThread(file,"E://test",104857600L);
        //104857600字节 == 10MB
    }
}

结果与单线程结果一致(除了时间)感兴趣的小伙伴可以尝试自己计时

总结:

通过以上两个例子不难发现其实多线程读取效率比单线程更低

这里简单的解释一下原因:关于机械磁盘只有在磁头放到正确的磁道上,然后等待相应的扇区转到该磁头下才可以读取磁盘数据,而io操作的对象就是磁盘上的数据,每增加一个线程就会相应的增加磁头移动的频率,而最耗时的其实并不是把数据从磁盘读取到磁头,而是磁头移动到正确的磁道上这个步骤。如果小伙伴们想要深入理解可以查阅其他相关资料,此处不再赘余

你可能感兴趣的:(深度探究java底层原理系列)