Day23
一、线程池
引入
一个线程完成一项任务所需时间为:
- 创建线程时间 - Time1
- 线程中执行任务的时间 - Time2
- 销毁线程的时间 - Time3
为什么需要线程池
- 线程池技术正式关注如何缩短或调整Time1和Time3的时间,从而提高程序性能。项目中可以啊Time1,Time3分别安排在项目启动和结束的时间段或者一些空闲的时间断
- 线程池不仅调整Time1,Time3产生的时间段,而且它还显著减少了创建线程的数目,提高线程的复用率
- 系统启动一个新线程的成本是比较高的,因为涉及与操作系统的交互,在这种情况下,使用线程池可以很好的提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,优先考虑线程池
Java提供的线程池
ExecutorService:线程池接口
Executors:创建各种线程池的工具类
public class Test {
public static void main (String[] args) {
//创建单个线程的线程池
ExecutorService pool = Executors.newSingleThreadExecutor();
//创建指定线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
//创建可缓存线程的线程池,自动回收60s闲置的线程
ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 1; i <= 100; i++) {
pool.execute(new Task(i));//提交任务,i为任务编号(方便理解)
}
pool.shutdown();//关闭线程池
}
}
public class Task implements Runnable {
private int i;
public Task(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " --> " + i);
try {
Thread.sleep(1000);
} catch (InterruptedExcepion e) {
e.printStackTrace();
}
}
}
深入源码
ExecutorService pool = Executors.newSingleThreadExecutor();
ExecutorService pool = Executors.newFixedThreadPool(3);
ExecutorService pool = Executors.newCachedThreadPool();
三种线程池底层都是ThreadPoolExecutor类的对象
-- 分析ThreadPoolExecutor类的构造方法源码--------------------------------
public ThreadPoolExecutor(
int corePoolSize, ------------- 核心线程数量
int maximumPoolSize, ------------- 最大线程数量
long keepAliveTime, ------------- 闲置时间,作用于核心线程数与最大线程数之间的线程
TimeUnit unit, ------------- keepAliveTime的时间单位(可以是毫秒、秒....)
BlockingQueue<Runnable> workQueue, -- 任务队列
ThreadFactory threadFactory, -------- 线程工厂
RejectedExecutionHandler handler ---- 达到了线程界限和队列容量时的处理方案(拒绝策略)
) {}
执行步骤:
1.创建线程池后
2.任务提交后,查看是否有核心线程:
3.1 没有 -> 就创建核心线程 -> 执行任务 -> 执行完毕后又回到线程池中
3.2 有 -> 查看是否有闲置核心线程:
4.1 有 -> 执行任务 -> 执行完毕后又回到线程池
4.2 没有 -> 查看当前核心线程数是否核心线程数量:
5.1 否 -> 就创建核心线程 -> 执行任务 -> 执行完毕后又回到线程池中
5.2 是 -> 查看任务列表是否装载满:
6.1 没有 -> 就放入列表中,等待出现闲置线程
6.2 装满 -> 查看是否有普通线程(核心线程数到最大线程数量之间的线程)
7.1 没有 -> 就创建普通线程 -> 执行任务 -> 执行完毕后又回到线程池中
7.2 有 -> 查看是否有闲置普通线程
7.1.1 有 -> 执行任务 -> 执行完毕后又回到线程池中
7.1.2 没有 -> 查看现在所有线程数量是否为最大线程数:
8.1 是 -> 执行处理方案(默认处理抛出异常)
8.2 否 ->就创建普通线程-> 执行任务 -> 执行完毕后又回到线程池中
注:
1.为了更好的理解,在这里区分核心线程和普通线程,实际上区分的没这么清楚,都是线程
2.默认的处理方案就是抛出RejectedExecutionException
总结:核心线程 -> 任务队列 -> 普通线程 -> 拒绝策略
-- 分析单个线程的线程池的源码 --------------------------------
ExecutorService pool = Executors.newSingleThreadExecutor();
new ThreadPoolExecutor(
1, -- 核心线程数量
1, -- 最大线程数量
0L, -- 闲置时间
TimeUnit.MILLISECONDS, -- 时间单位(毫秒)
new LinkedBlockingQueue<Runnable>() -- 无界任务队列,可以无限添加任务
)
-- 分析指定线程的线程池的源码 --------------------------------
ExecutorService pool = Executors.newFixedThreadPool(3);
new ThreadPoolExecutor(
nThreads, -- 核心线程数量
nThreads, -- 最大线程数量
0L, -- 闲置时间
TimeUnit.MILLISECONDS, -- 时间单位(毫秒)
new LinkedBlockingQueue<Runnable>()-- 无界任务队列,可以无限添加任务
)
-- 创建可缓存线程的线程池 -----------------------------------
ExecutorService pool = Executors.newCachedThreadPool();
new ThreadPoolExecutor(
0, -- 核心线程数量
Integer.MAX_VALUE,-- 最大线程数量
60L, -- 闲置时间
TimeUnit.SECONDS, -- 时间单位(秒)
new SynchronousQueue<Runnable>() -- 直接提交队列(同步队列):没有容量队列
任务队列详解
队列名称 | 详解 |
---|---|
LinkedBlockingQueue无界任务队列 | 使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题 |
SynchronousQueue 同步任务队列 直接提交任务队列 | 使用直接提交任务队列,队列没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。任务队列为SynchronousQueue,创建的线程数大于maximumPoolSize时,直接执行了拒绝策略抛出异常。 使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的线程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略; |
ArrayBlockingQueue有界任务队列 | 使用有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。 |
PriorityBlockingQueue优先任务队列 | 使用优先任务队列,它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。 |
对优先队列的使用说明:
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 200, 1000, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 1; i <= 10; i++) {
pool.execute(new Task(i));
}
pool.shutdown();
}
}
class Task implements Runnable,Comparable<Task>{
private int priority;
public Task(int priority) {
this.priority = priority;
}
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " priority:" + this.priority);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//当前对象和其他对象做比较,当前优先级大就返回-1,优先级小就返回1,值越小优先级越高
@Override
public int compareTo(Task o) {
return (this.priority>o.priority)?-1:1;
}
}
总结:除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行,且线程池的线程数一直为corePoolSize,也就是只有一个。
拒绝策略
ThreadPoolExecutor自带的拒绝策略有四种,都实现了RejectedExecutionHandler接口
比如:new ThreadPoolExecutor.AbortPolicy()
拒绝策略 | 解释 |
---|---|
AbortPolicy | 当有任务添加到线程池被拒绝时,会抛出RejectedExecutionException异常,线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。 |
DiscardPolicy | 当有任务添加到线程池被拒绝时,直接丢弃,其他啥都没有 |
CallerRunsPolicy | 当有任务添加到线程池被拒绝时,线程池会将被拒绝的任务添加到线程池正在运行的线程中去运行。 一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大 |
DiscardOledestPolicy | 当有任务添加到线程池被拒绝时,线程池会丢弃阻塞队列中末尾的任务(最老的任务–第一个添加的任务),然后将被拒绝的任务添加到末尾。 如果项目中有允许丢失任务的需求,可以使用 |
自定义拒绝策略
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString()+"执行了拒绝策略");
}
});
for (int i = 1; i <= 10; i++) {
pool.execute(new Task());
}
pool.shutdown();
}
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
自定义线程池原因
在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面使线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活,而且有资源耗尽的风险(OOM - Out Of Memory )。
一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但这种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况
自定义线程池
前提学习线程池中如何创建线程:
线程池中线程就是通过ThreadPoolExecutor中的ThreadFactory,线程工厂创建的。那么通过自定义ThreadFactory,可以按需要对线程池中创建的线程进行一些特殊的设置,如命名、优先级等
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
10,20, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1),
new ThreadFactory() {
int threadNum = 1;
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r,"线程"+threadNum++);
return t;
}
}, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString()+"执行了拒绝策略");
}
});
for (int i = 1; i <= 10; i++) {
pool.execute(new Task());
}
pool.shutdown();
}
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ThreadPoolExecutor扩展
ThreadPoolExecutor扩展主要是围绕beforeExecute()、afterExecute()和terminated()三个方法的重写, 通过这三个方法我们可以监控每个任务的开始和结束时间,或者其他一些功能。
方法 | 解释 |
---|---|
beforeExecute | 线程池中任务运行前执行 |
afterExecute | 线程池中任务运行完毕后执行 |
terminated | 线程池退出后执行 |
下面我们可以通过代码实现一下
ThreadPoolExecutor pool = new ThreadPoolExecutor(
10, 10, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10),
new ThreadFactory() {
int threadNum = 1;
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r,"线程"+threadNum++);
return t;
}
}, new ThreadPoolExecutor.AbortPolicy()
){
@Override
protected void beforeExecute(Thread t,Runnable r) {
System.out.println("准备执行:"+ Thread.currentThread().getName());
}
@Override
protected void afterExecute(Runnable r,Throwable t) {
System.out.println("执行完毕:"+ Thread.currentThread().getName());
}
@Override
protected void terminated() {
System.out.println("线程池退出");
}
};
for (int i = 1; i <= 10; i++) {
pool.execute(new Task());
}
pool.shutdown();
}
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("执行中:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程池线程数量
实际工作中使用 sysbench多线程性能测试工具
带返回值的任务类 – Callable
计算任务:一个包含了2万个整数的数组,分拆了多个线程来进行并行计算,最后汇总出计算的结果
注意:使用线程池 + 带有返回值的任务类去解决该需求
public class Test {
public static void main(String[] args) {
//常见数据源
int[] arr = new int[20000];
//初始化数据源 - [1,2,3,4,...,20000]
for (int i = 0; i < arr.length; i++) {
arr[i] = i+1;
}
//创建线程池
FastThreadPool pool = new FastThreadPool(4,4,1);
//提交任务,并返回任务执行完毕时的结果对象
Future<Integer> future1 = pool.submit(new Task(arr, 0, 5000));
Future<Integer> future2 = pool.submit(new Task(arr, 5000, 10000));
Future<Integer> future3 = pool.submit(new Task(arr, 10000, 15000));
Future<Integer> future4 = pool.submit(new Task(arr, 15000, 20000));
//获取任务执行结果,并合并
int result = futurel.get() + future2.get() + future3.get() + future4.get();
System.out.println(result);
pool.shutdown();
}
}
class Task implements Callable<Integer> {
private int[] arr;
private int startIndex;
private int endIndex;
public Task(int[] arr, int startIndex, Int enIndex) {
this.arr = arr;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = startIndex; i < endIndex; i++) {
sum += arr[i];
}
return sum;
}
}
二、File – 文件类
理解:File类的对象可以描述一个目录或者文件
注意:
- File类的对象可以获取文本信息,但是不能操作文件的内容
- 要想操作文件的内容需要通过IO流技术
需求:使用File类的常用方法获取文件的信息
public class Test {
public static void main(String[] args) {
File file = new File("D:\\蔡徐坤\\合计.txt");
System.out.println("获取文件名:" + file.getName());
System.out.println("获取文件绝对路径:" + file.getAbsolutePath());
System.out.println("获取文件是否可读:" + file.canRead());
System.out.println("获取文件是否可写:" + file.canWrite());
System.out.println("获取文件是否隐藏:" + file.isHidden());
System.out.println("获取文件大小(字节):" + file.length());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String datetime = sdf.format(file.lastModified());
System.out.println("获取文件最后的修改时间:" + datetime);
}
}
绝对路径 和 相对路径
绝对路径:指定盘符的路径
相对路径:相对于当前项目下的路径
public class Test {
public static void main(String[] args) {
File file = new File("file.txt");
//D:\workspace\Day23\file.txt
System.out.println("绝对路径:" + file.getAbsolutePath());
//file.txt
System.out.println("相对路径:" + file.getPath());
}
}
需求:通过程序,判断指定路径的文件是否存在,如果不存在,则创建该文件
-
目录已存在的情况
public static void main(String[] args) throws IOException { File file = new File("file01\\file.txt"); //判断是否有当前文件 if(!file.exists()){ //创建文件 file.createNewFile(); } }
-
有一个层级的目录不存在的情况
public static void main(String[] args) throws IOException { File file = new File("file01\\file.txt"); //获取上一级file对象 -- file01 File parentFile = file.getParentFile(); if(!parentFile.exists()){ //创建一层文件夹 parentFile.mkdir(); } //判断是否有当前文件 if(!file.exists()){ //创建文件 file.createNewFile(); } }
-
有多个层级的目录不存在的情况
public static void main(String[] args) throws IOException { File file = new File("file01\\file02\\file03\\file.txt"); //获取上一级file对象 -- file01\\file02\\file0 File parentFile = file.getParentFile(); if(!parentFile.exists()){ //创建多层文件夹 parentFile.mkdirs(); } //判断是否有当前文件 if(!file.exists()){ //创建文件 file.createNewFile(); } }
需求:输出指定目录下的所有文件信息
public static void main(String[] args) {
File file = new File("D:\\飞秋共享");
//获取当前目录下所有的文件名
// String[] list = file.list();
// for (String str : list) {
// System.out.println(str);
// }
//获取当前目录下所有的文件对象
File[] listFiles = file.listFiles();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
for (File f : listFiles) {
String name = f.getName();
String datetime = sdf.format(f.lastModified());
System.out.println(name + " -- " + datetime);
}
}
-
要求只输出文件后缀名为xmind的文件
public static void main(String[] args) { File file = new File("D:\\蔡徐坤"); //获取当前目录下所有的文件名 // String[] list = file.list(); // for (String str : list) { // if(str.endsWith(".xmind")){ // System.out.println(str); // } // } //获取当前目录下所有的文件对象 File[] listFiles = file.listFiles(); for (File f : listFiles) { String name = f.getName(); if(f.isFile() && name.endsWith(".xmind")){ System.out.println(name); } } }
-
根据API的过滤器来完成该功能
public static void main(String[] args) { File file = new File("D:\\蔡徐坤"); File[] listFiles = file.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { File f = new File(dir, name); if(f.isFile() && name.endsWith(".xmind")){ return true; } return false; } }); for (File f : listFiles) { System.out.println(f); } }
-
需求继续跟进,列出当前目录及子目录中符合该条件的文件信息(递归)
public static void main(String[] args) { File file = new File("D:\\蔡徐坤"); method(file, ".txt"); } public static void method(File file,String suffix){ File[] files = file.listFiles(); for (File f : files) { if(f.isDirectory()){ method(f, suffix); }else if(f.isFile()){ if(f.getName().endsWith(suffix)){ System.out.println(f.getAbsolutePath()); } } } }
三、IO流
概念
I - in - 输入流:将数据传入到程序内容 -> read()
O - out - 输出流:将程序里的数据传出去 -> writer()
流:一点点,像水流一般的传输
注意:输入、输出的方向是站在程序的角度考虑
流的分类
按照方向分:输入流、输出流
按照单位分:字节流、字符流
按照功能分:基础流/节点流、处理流
注意:
- 字符流底层使用字节流实现,字符流其实就是字节流+编码器
- 处理流的功能更加强大,效率更高
- 处理流往往包含了基础流(装饰器模式) – new 处理流(new 基础流());
存储进制
1024KB = 1MB
1024MB = 1GB
1024GB = 1TB
1024TB = 1PB
字节流
InputStream – 字节输入流的基类(抽象类)
OutputStream – 字节输出流的基类(抽象类)
FileInputStream extends InputStream – 文件字节输入流
FileOutputStream extends OutputStream – 文件字节输出流
利用字节输出流 向 文件写入数据
-
文件存在的情况
-
不处理异常的情况
public static void main(String[] args) throws IOException { //1.创建流对象 FileOutputStream fos = new FileOutputStream("cxk.txt"); //2.写入数据 //fos.write(97);//写入字符码值 //fos.write("123abc".getBytes());//写入字节数组 fos.write("123abc".getBytes(), 3, 3);//写入字节数组,偏移量,写入长度 //3.关闭资源 fos.close(); }
-
文件不存在的情况
- 经验:
- 所有的输出流,当文件不存在时,都会创建文件,再写入
- 所有的输入流,当文件不存在时,就会报错 – FileNotFoundException文件未找到异常
- 经验:
-
在文件末尾追加
经验:考虑输出流的基础流
-
处理异常的情况
public static void main(String[] args) { FileOutputStream fos = null; try { //1.创建流对象 fos = new FileOutputStream("cxk.txt", true); //2.写入数据 fos.write("123abc".getBytes()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { //3.关闭资源 if(fos != null){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } }
利用字节输入流 读取文件里的数据
-
不处理异常的方式
-
文件存在的情况
-
文件不存在的情况
- 经验:
- 所有的输出流,当文件不存在时,都会创建文件,再写入
- 所有的输入流,当文件不存在时,就会报错 – FileNotFoundException文件未找到异常
public static void main(String[] args) throws IOException { //1.创建流对象 FileInputStream fis = new FileInputStream("cxk.txt"); //2.读取数据 //read():一个一个的读取字符,读取到文件末尾返回-1 int read = fis.read(); System.out.println((char)read); read = fis.read(); System.out.println((char)read); read = fis.read(); System.out.println((char)read); read = fis.read(); System.out.println((char)read); read = fis.read(); System.out.println((char)read); read = fis.read(); System.out.println((char)read); read = fis.read(); System.out.println(read); //3.关闭资源 fis.close(); }
public static void main(String[] args) throws IOException { //1.创建流对象 FileInputStream fis = new FileInputStream("kk.txt"); //2.读取数据:一个一个字节的循环读取 int read; while((read = fis.read()) != -1){ System.out.println((char)read); } //3.关闭资源 fis.close(); }
public static void main(String[] args) throws IOException { //1.创建流对象 FileInputStream fis = new FileInputStream("cxk.txt"); //2.读取数据:一个一个字节数组的循环读取 //fis.read(bs):读取bs长度的数据放入到数组中,返回读取到的有效字节数;读取到文件末尾返回-1 byte[] bs = new byte[1024]; int len; while((len = fis.read(bs)) != -1){ System.out.println(new String(bs, 0, len)); } //3.关闭资源 fis.close(); }
- 处理异常的方式
public static void main(String[] args) { FileInputStream fis = null; try { //1.创建流对象 fis = new FileInputStream("cxk.txt"); //2.读取数据:一个一个字节数组的循环读取 //fis.read(bs):读取bs长度的数据放入到数组中,返回读取到的有效字节数;读取到文件末尾返回-1 byte[] bs = new byte[1024]; int len; while((len = fis.read(bs)) != -1){ System.out.println(new String(bs, 0, len)); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { //3.关闭资源 if(fis != null){ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } }
- 经验:
拷贝文件
思路:读取源文件,写入目标文件
public class Copy01 {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("源文件.mp4");
fos = new FileOutputStream("目标文件.mp4");
//一个一个字节去拷贝
int read;
while ((read = fis.read() != -1)) {
fos.write(read);
}
} catch (IOException e) {
e.printstackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class Copy02 {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("源文件.mp4");
fos = new FileOutputStream("目标文件.mp4");
//一个一个字节数组去拷贝
byte[] bs = new byte[1024];
int len;
while((len = fis.read(bs)) != -1){
fos.write(bs, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally{
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class Copy03 {
public static void main(String[] args) {
try(
//在这个位置创建的流,会在try执行完毕后自行关闭
FileInputStream fis = new FileInputStream("源文件.mp4");
FileOutputStream fos = new FileOutputStream("目标文件.mp4");
) {
//一个一个字节数组去拷贝
byte[] bs = new byte[1024];
int len;
while((len = fis.read(bs)) != -1){
fos.write(bs, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结:
- 线程池
理解
Java自带的线程池
线程池的7大参数及调用过程
为什么不适用java自带的线程池的原因
自定义线程池(自定义线程工厂、自定义拒绝策略) - File类
- IO流 – 字节流