多线程-线程组-线程池-异常
程序员文章站
2022-05-05 16:38:08
...
一.异常
1)异常结构图解
2)异常基本概念
1)概念:
在程序执行的时候,程序出现问题(Bug)
2)异常结构
*Throwable:Java语言中所有错误或异常的超类(父类)
*两个子类:
**Error:严重问题(一些其他原因导致的):需要借助第三方技术解决
**Eeception:不是很严重的问题(程序出现不严谨或者一些编译问题)
<1>运行时期异常:RuntimeException
运行时期出现的原因:开发者编码的时候,代码逻辑不严谨!
<2>编译时器异常:只要不是RuntimeException的子类都属于编译时期异常ParseException(属于编译时期异常)
jvm在检查语法的时候,针对某些特定的方法针对方法进行判断如果不解决编译时期异常,程序执行不了的!
3)异常处理方案(两种):
<1>try...catch...finally:标准格式(具体异常具体常量,尽量不使用大的异常:Execption)
*变形格式:
try...catch...
try...catch...catch...
try...finally...(多线程中使用)
*使用格式:
try{
可能出现错误的代码;
}catch(异常类名 变量名){
处理异常;
}finally(){
释放资源;
}
*执行流程:
**首先加载try里面代码如果try里面的有问题---->出现异常了
内存中:由Jvm产生一个异常类的实例 new XXException()自动判断
**需要和catch后面的进行匹配,如果互相匹配了,就执行catch里面的语句,处理异常
*如何处理多个异常:
**一个一个处理:
每一个代码都try...catch...
try...catch...
**变形格式:
try...catch...catch...catch.....
**JDK7以后:有一种的新的处理方式: (源码里面会看到,看懂格式即可)
try{
可能出现的代码;
}catch(异常类名1 | 异常类名2 | 异常类名3 变量名){
处理异常;
}
<2>throw/throws :直接抛出异常(具体异常具体抛出,尽量不使用Execption:异常抛出)
*编译时期使用:调用者必须处理
*运行时期异常:调用者不需要处理
4)编译时期异常和运行时期异常区别:
*编译时期异常:开发者必须要处理,否则编译不通过,程序运行不了! 调用者必须针对编译时期的异常进行处理(在方法体中使用的捕获异 常,调用者不需要处理如果是将异常抛出在方法声明上,调用者需要处理)
*运行时期异常:一般情况,针对代码可以不需要显示处理,在代码中加入一些逻辑判断
**要么显示处理
**要么代码中加入一些判断即可!
5)异常处理的注意事项:
*子类继承父类的时候,如果父类的该方法本身就存在异常,那么子类重写父类该方法的时候必须是该异常或者是异常的子类
(简单记:父亲坏了,儿子不能比父亲还坏)
*子类继承父类,父类的成员方法如果没有当前异常,子类不能抛出throws,只能try...catch
6)try...catch:语句快捷键:shift+Alt+z
3)异常类的方法:Throwable相关的方法
1)public String getMessage():获取异常的相关的消息字符串
2)public String toString():异常信息的简单描述
此对象的类的 name :异常类名 ": "(冒号和一个空格) : ": " 调用此对象 getMessage() 方法的结果
3)public void printStackTrace():追踪至异常类名的错误输出流中
4)捕获异常的标准格式使用:
try{
可能出现问题的代码;
}catch(异常类名 变量名){
处理异常;
}finally{
在IO流中/JDBC中释放相关的系统资源;
}
finally中的语句一定会执行,除非是在执行finally代码之前,jvm退出了!
5)面试题:
1. throw/throws的区别?
1)位置不同
*throws:是方法声明上抛出
*throw:是方法体中抛出
2)两个关键字后面跟的类型不同
*throws:后面跟的是异常类名.而且可以跟多个类名,中间使用逗号隔开
*throw:后面跟着的是异常类名,具体异常具体对待(格式: throw new 异常类名();不能跟多个,只能有一个)
3)
*throws:表示产生异常的一种可能性
*throw:表示产生异常的一种肯定性
4)
*throws:抛出的异常由他的调用者进行处理
*throw:方法中的语句进行处理(加入一些逻辑判断)
2.在try...catch...finally中,catch中如果有return语句的话,finally中代码还会执行吗? 如果会,在catch之前,还是catch之后?
*finally中的语句一定会执行,除非是在执行之前,jvm终止了,才不会执行!会执行,在执行finally语句之前,方法已经形成回路径,具体在执行前就已经返回了(catch和finally之间就返回结果了);
*代码示例:
/*
private static int getNum(int a) {
System.out.println(a);
a = 10 ;
try {
a = 30 ;
System.out.println(a/0);//程序出问题了
} catch (Exception e) {
a = 40 ; //40赋值给a
return a; //return 40 :这个方法在这块就存在回路径:return 40
}finally {
//这里一般都在释放资源
a = 50 ;
//return a ;
}
return a; //return 返回之前的路径
}
*/
二.多线程
1)多线程概述
##先引入单线程概念:在执行过程中,只有一条路径,单线程运行
1)概念:在执行的执行过程中,有多条路径
进程:能够调用系统资源的独立单位打开任务管理器---->每一个客户端软件---->开启一个进程
线程:线程依赖于进程存在线程是进程中某一个任务,举例:360软件(开启一个进程)清理内存的同时,开启一个任务:查杀病毒
2)多线程的意义:
为了让多个任务在互相抢占CPU的执行权! ##注意:线程的执行具有随机性
2)java实现多线程:
1)实现多线程的环境:创建系统资源(产生进程)----->Java语言不能系统资源--->所以Jdk提供了一个类:
Thread类:是封装的线程类(里面一些功能的底层实现(C/C++语言)系统资源创建)Java 虚拟机允许应用程序并发地运行多个执行线程。
2)实现:
*实现方式1: 继承关系(具有局限性,限制子类不能再继承别的类)
<1>自定义类 继承自Thread类(线程类)
<2>在当前自定义的类中:重写run方法 (执行耗时的操作)(jvm会自动的让子线程执行run方法)
在"用户线程"main ,创建该类对象
启动线程 (执行子线程):start()
/*
class MyThread exnteds Thread{
@Override
public void run(){
for(int x = 0 ; x < 100 ; x ++) {
System.out.println(this.getName()+":"+x);
}
}
}
public class Test{
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();//启动线程
}
}
*/
*实现方式2:实现Runnable接口:
<1>定义一个实现了Runnable接口的类(MyRunnable类)
<2>在MyRunnable类中重写run方法
<3>在主线程中创建MyRunnable类的对象mr:MyRunnable mr = new MyRunnable();
<4>利用mr构造一个Thread对象tr:Thread tt=new Thread(mr);
<5>调用tr的start方法启动线程:tr.start()
/*
class MyRunnable implements Runnable{
@Override
public void run(){
for(int x = 0 ; x < 100 ; x ++) {
System.out.println(this.getName()+":"+x);
}
}
}
public class RunnableDemo{
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();
}
}
*/
*实现方式3:匿名内部类
/*
new Thread(new Runnable() {
@Override
public void run() {
for(int x = 0 ; x < 100 ; x ++) {
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}).start();
*/
3)线程的生命周期:
*新建: NEW
*运行: RUNNABLE
*阻塞:BLOCKED
*等待(线程在执行期间,利用wait()方法):WAITING
*超时等待(超过一定时间,然后等待结束):TIMED_WAITING
*终止:TERMINATED
4)Thread类
1)构造方法:
public Thread(String name):设置指定的线程名称
public Thread():空参构造
2)相关设置线程名称/获取线程名称的方法:
public final void setName(String name):设置线程名称
public final String getName():获取线程名称
3)启动线程方法:
public void start():启动一个分支线程,在JVM中开辟一个新的栈空间.
public void run():如果该线程是使用一个单独的 Runnable运行对象的构造,然后 Runnable对象的 run方法被调用;否则,该方法不返回。子Thread应重写此方法。(run方法在分支栈的底部,他和main方法平级)
4)相关功能:
public static void sleep(long millis):睡眠(让当前线程进入睡眠状态,放弃占有CPU,让给其他线程使用)
public static void yield():当前线程暂停,回到就绪状态,让给其他线程
public final void join():等待该线程终止(等待该线程终止,下一个线程才可以运行)
public final void stop(Throwable obj):强迫线程停止执行,已经过时,但可以使用
public void interrupt():中断线程(中断线程的状态,不是停止执行)
5)线程的优先级
1) 线程的优先级: 也遵循线程的执行具有随机性!
public static final int MAX_PRIORITY 10 :最大优先级 10
public static final int MIN_PRIORITY 1 :最小优先级 1
public static final int NORM_PRIORITY 5 :默认优先级 5
优先级越大的线程:抢占到CPU的执行权越大
越小的优先级的线程:抢占到CPU的执行权越小
默认的优先级:抢占到CPU的执行权相等的
2)设置优先级方法:
public final int getPriority():获取优先级
public final void setPriority(int newPriority):设置优先级
newPriority : 1:最小优先级
5:默认的优先级
10:最大优先级
##唤醒睡眠的线程(中断睡眠)(如图所示)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZlczosLE-1598170566562)(E:\千峰-文件\截屏笔记\多线程-中断睡眠.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G4TEuWfw-1598170566563)(E:\千峰-文件\截屏笔记\多线程-中断睡眠2.png)]
##在java中如何强制终止一个线程的执行
package Test01;
//在java中如何强制终止一个线程的执行
public class ThreadDemo {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread tr = new Thread(r);
tr.setName("线程1");
tr.start();
//模拟5秒
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止线程
r.run = false ;
}
}
class MyRunnable implements Runnable{
boolean run = true ;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(run) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//终止当前异常
return ;
}
}
}
}
6)电影院售票案例
1)案例引出
*条件:三个窗口-->三个线程同时售卖100张票
*实现方法:
**方式1:继承Thread类
**方式2:实现Runnable接口
2)多线程并发环境下,数据安全问题
*什么时候数据在多线程并发的环境下会存在安全问题(检验一个程序是否存在安全问题的标准)
三个条件:
*多线程并发
*有共享数据
*共享数据有修改(操作)的行为
*怎么解决线程安全问题:
*条件一和条件二不能改动,只能改动条件三
*线程排队执行(这种专业机制称为:线程同步机制)(会降低效率)
#线程同步涉及两个专业术语:
异步编程模型(并发):多线程并发
同步编程模型(排队):两个线程之间发生了等待关系.
*线程同步机制语法(格式):
关键字:synchronized(同步锁(悲观锁))
锁对象:任意的java对象(必须是共享,必须是同一把锁)
/*
synchronized(需要排队线程的共享对象<锁对象>){
多条语句对共享数据的操作;
}
*/
3)正确代码示例
package com.qianfeng.thread_05;
public class SellTicketDemo {
public static void main(String[] args) {
//Thread类本身就实现了Runnable
//创建资源类对象 (被共享)
SellTicket st = new SellTicket() ;
//创建线程类对象
Thread t1 = new Thread(st, "窗口1") ;
Thread t2 = new Thread(st, "窗口2") ;
Thread t3 = new Thread(st, "窗口3") ;
//启动线程
t1.start();
t2.start();
t3.start();
}
}
------------------------------------------------------------------------------------------------------------
package com.qianfeng.thread_05;
public class SellTicket implements Runnable {
//声明一个票
private static int tickets = 100;
private Object obj = new Object() ;
@Override
public void run() {
while(true) {
//t1,t2,t3
synchronized(obj) {
if(tickets>0) { //100
//模拟真实的延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}
}
}
}
}
7)多线程中的同步机制
1)概念引入:
<1>线程同步是为了确保线程安全,所谓线程安全指的是多个线程对同一资源进行访问时,有可能产生数据不一致问题,导致线程访问的资源并不是安全的。如果多线程程序运行结果和单线程运行的结果是一样的,且相关变量的值与预期值一样,则是线程安全的。
<2>一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java里边就是拿到某个同步对象的锁(一个对象只有一把锁); 如果这个时候同步对象的锁被其他线程拿走了,他(这个线程)就只能等了(线程阻塞在锁池 等待队列中)。 取到锁后,他就开始执行同步代码(被synchronized修饰的代码);线程执行完同步代码后马上就把锁还给同步对象,其他在锁池中 等待的某个线程就可以拿到锁执行同步代码了。这样就保证了同步代码在统一时刻只有一个线程在执行。
2)同步代码块 同步代码块:比较灵活
synchronized(线程共享对象){
同步代码块;
}
3)同步方法:如果一个方法的方法体就是一个同步代码块的话,将synchronzied关键字放到方法声明上形式一个同步方法(非静态)
格式:
权限修饰符 synchronized 返回值类型 方法名(形式参数){
....
}
4)在静态方上使用synchronized
表示找类锁:类锁只有一把
##锁对象:
*同步方法的锁对象:是this 代表当前类对象的地址值引用!
*静态的同步方法的锁对象:是当前类名.class属性(可以获取当前类的字节码文件对象 :Class<T>)
8)使用同步代码解决线程安全问题引发–>死锁
1)概念引入:
解决了线程安全问题,同时伴随一个问题:当前两个或者两个以上的线程在互相争夺同一个资源的时候会造成一种现象,死锁! (每一个线程都在等待对方线程释放锁)
2)死锁举例:
生产者消费者模式:生产者线程,消费者线程
3)代码举例(反例论证):
/*
package com.qianfeng.thread_09;
//自定义类中,创建两把锁对象
public class MyDieLock {
public static final Object objA = new Object() ;
public static final Object objB = new Object() ;
}
------------------------------------------------------------------------------------------------------------
package com.qianfeng.thread_09;
public class DieLock implements Runnable {
//声明一个变量
private boolean flag ;
public DieLock(boolean flag) {
this.flag = flag ;
}
//t1,t2
@Override
public void run() {
if(flag) {
synchronized (MyDieLock.objA) { //使用objA锁
System.out.println("if objA"); //"if objA"
synchronized (MyDieLock.objB) { //t1线程等待t2线程释放objB这个锁
System.out.println("if objB");
}
}
}else {
synchronized(MyDieLock.objB) { //使用t2:objB锁
System.out.println("else objB");//"else objB"
synchronized(MyDieLock.objA) {
System.out.println("else objA"); //t2线程等待t1线程释放objA这个锁
}
}
}
}
}
------------------------------------------------------------------------------------------------------------
public class DieLockDemo {
public static void main(String[] args) {//用户线程
//主线程中
//创建资源了对象
DieLock d1 = new DieLock(true) ;
DieLock d2 = new DieLock(false) ;
//创建线程对象
Thread t1 = new Thread(d1) ;
Thread t2 = new Thread(d2) ;
t1.start();
t2.start();
/*
* 情况1: t1线程先抢占到了
* if objA
else objB
情况2:t2线程先抢占到CPU执行权
else objB
if objA
情况3:理想状态 : 假设t1抢占到了 瞬间t2页抢占到了,并且同时将objB释放锁了
if objA else objB
if objB else objA
* */
}
}
*/
9)死锁的解决办法—>Java中的等待唤醒机制
1)概念:线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。 而这种手段即—— 等待唤醒机制。
2)等待唤醒机制所涉及到的方法:
*wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。
*notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。
*notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。
3)问题案例引入:生产者-消费者问题(java中的等待唤醒机制)
4)分析:
生产者线程:判断是否有数据,没有数据,等待产生数据
消费者线程:判断是否有数据,如果有数据,等待消费掉
5)代码案例:(学生对象)
/*
package com.home.work_01;
/*1.将上课中的等待唤醒机制案例改造----》
Student类中的成员私有化
提供对外的公共的同步方法,设置学生数据和获取学生数据
生产者线程和消费者线程只需要调用学生类中的两个方法即可*/
public class Test {
public static void main(String[] args) {
//创建学生共享对象
Student stu = new Student();
//创建线程对象
SetStudent ss = new SetStudent(stu);
GetStudent gs = new GetStudent(stu);
//创建线程类对象
Thread t1 = new Thread(ss,"产生学生数据");
Thread t2 = new Thread(gs,"获取学生数据");
//Thread t1 = new Thread(ss);
//Thread t2 = new Thread(gs);
//启动线程
t1.start();
t2.start();
}
}
------------------------------------------------------------------------------------------------------------
package com.home.work_01;
public class Student {
private String name;
private int age;
boolean flag;
public Student() {
super();
// TODO Auto-generated constructor stub
}
public Student(String name, int age, boolean flag) {
this.name = name;
this.age = age;
this.flag = flag;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the age
*/
public int getAge() {
return age;
}
/**
* @param age the age to set
*/
public void setAge(int age) {
this.age = age;
}
/**
* @return the flag
*/
public boolean isFlag() {
return flag;
}
/**
* @param flag the flag to set
*/
public void setFlag(boolean flag) {
this.flag = flag;
}
}
------------------------------------------------------------------------------------------------------------
package com.home.work_01;
public class SetStudent implements Runnable {
private Student stu ;
private int count = 0;
public SetStudent(Student stu) {
super();
this.stu = stu;
}
@Override
public void run() {
while(count<100) {//循环生产学生数据
//加锁
synchronized (stu) {
//如果没有数据就生产,有就等待
if(stu.flag) {
try {
stu.wait();//执行之后,立马释放锁
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/*//睡眠
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}*/
//分开生产数据
if(count % 2 == 0) {
stu.setName("王伟");
stu.setAge(21);
System.out.println("生产");
}else {
stu.setName("李明");
stu.setAge(24);
System.out.println("生产");
}
count++;
//修改标记,已经生产好了数据
stu.flag = true ;
//告诉获取数据的线程,有数据,可以结束等待,继续获取
stu.notify();
}
}
}
}
------------------------------------------------------------------------------------------------------------
package com.home.work_01;
public class GetStudent implements Runnable{
private Student stu ;
private int count = 0;
public GetStudent(Student stu) {
super();
this.stu = stu;
}
@Override
public void run() {
while(count<100) {
//加锁
synchronized (stu) {
//如果有数据就获取,没有就等待
if(!stu.flag) {
try {
stu.wait();//执行之后,立马释放锁
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/*//睡眠
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}*/
//分开获取数据
if(count%2==0) {
System.out.println(Thread.currentThread().getName()+
": 姓名 :"+stu.getName()+", 年龄 : "+stu.getAge());
}else {
System.out.println(Thread.currentThread().getName()+
": 姓名 :"+stu.getName()+", 年龄 : "+stu.getAge());
}
count++;
//修改标记,已经获取好了数据
stu.flag = false ;
//告诉生产数据的线程,已经获取完数据,可以结束等待,继续生产
stu.notify();
}
}
}
}
------------------------------------------------------------------------------------------------------------
*/
10)面试题
1)JVM是多线程的吗?
答:JVM是多线程的,至少有两条线程
<1>main方法:"用户线程"或者"主线程" ,JVM调用main方法
<2>垃圾回收线程,在内存中不断的通过垃圾回收器释放空间
2)线程有几种状态? (6种)
*新建线程: NEW
*线程就绪: RUNNABLE
*线程阻塞:BLOCKED
*线程等待(线程在执行期间,利用wait()方法):WAITING
*超时等待(超过一定时间,然后等待结束):TIMED_WAITING
*线程死亡:TERMINATED
3)关于Thread.sleep()方法的一个面试题:
如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s1jED6fp-1598170566564)(E:\千峰-文件\截屏笔记\多线程中sleep()]方法,面试题.png)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fPixDCLf-1598170566566)(EL:\千峰-文件\截屏笔记\多线程中sleep()]方法,面试题2.png)
4)多线程继承自Thread类和Runnable接口方式的区别?
Runnable接口:更能体现描述"数据共享"的概念: (面向接口编程) (推荐这个方式!)
*自定义类(资源类)--->实现Runnable接口
*接口中只有一个run方法() (抽象的),解决了多线程实现方式1单继承的局限性
该方式使用到了静态代理(Thread源码类本身就是实现了Runnable接口)
Thread类:体现不出 "数据共享"的概念
*创建自定义类的同时,创建线程对象
*从内存角度:三个栈内存指向三个堆内存
5)sleep(long millis)方法和wait()方法区别?
*来源不同:
sleep(long millis):是Thread 类中的方法
wait():是Object类中的方法:需要使用锁对象调用的
*调用方法的时候是否释放锁:
sleep(long millis):线程睡眠多少毫秒数据(期间,线程是暂停状态)
wait():线程等待,调用该方法,会立即释放锁对象!利用wait方法会释放锁对象,然后使用锁对象调用notify(),来完成Java中等待唤醒机制!
*共同点:
wait()方法和sleep()方法都可能会出现中断异常!(InterruptedExecption)
6)wait()和notify()这些方法为什么不定义Thread类中,而是在Object类中呢?
Java提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。简单的说,由于wait,notify,notifyAll都是锁级别的操作,所以把他们定义在object类中因为锁属于对象。
7)代理模式?
代理:就是让别人替自己完成事情!
*静态代理:(Thread类本身就用到了静态代理 Thread implements Runnable开发中:自定义了一个类 实现Runnable接口)
**真实角色: 只专注自己的事情
**代理角色:帮助真实角色完成不了的事情!
**特点:
真实角色和代理角色都需要去实现相关的接口(必须是同一个接口)!
代理角色重写接口中的方法是对真实角色的方法进行了增强(扩展)!
/*静态代理,代码示例(<1>自己买票<2>黄牛买票)
------------------------------------------------------------------------------------------------------------
package com.qianfeng_01;
//主线程,测试类
public class Test {
public static void main(String[] args) {
//创建自己买票对象,并测试
BuyTicketDemo btd = new BuyTicketDemo();
btd.theAgent();
System.out.println("---------------------------------");
//创建代理买票对象,测试
BuyTicketAgent bta = new BuyTicket(btd);
bta.theAgent();
}
}
------------------------------------------------------------------------------------------------------------
package com.qianfeng_01;
//黄牛代理买票功能接口
public interface BuyTicketAgent {
void theAgent();
}
------------------------------------------------------------------------------------------------------------
package com.qianfeng_01;
//黄牛代理类,增强自己买票功能,
public class BuyTicket implements BuyTicketAgent {
private BuyTicketDemo buy ; //调用自己买票对象
public BuyTicket(BuyTicketDemo buy) {
this.buy = buy;
}
@Override
public void theAgent() {
System.out.println("黄牛帮我买票...");
buy.theAgent();
System.out.println("付给黄牛钱...");
}
}
------------------------------------------------------------------------------------------------------------
package com.qianfeng_01;
//自己买票
public class BuyTicketDemo implements BuyTicketAgent{
@Override
public void theAgent() {
// TODO Auto-generated method stub
System.out.println("我买到票了...");
}
}
------------------------------------------------------------------------------------------------------------
*/
*动态代理:
JDK动态代理(反射中讲: Proxy)
cglib动态代理 (Spring)
三.线程组 :ThreadGroup(了解)
1)概念:
*ThreadGroup:表示一个线程组,是一个线程的集合
2)Thread类中:
*public final ThreadGroup getThreadGroup()返回该线程所属的线程组 一个线程在内存中可以由多个线程组组成!
*线程组(默认main:用户线程)
3)通过构造方法:设置线程组名称
public ThreadGroup(String name):构造线程组对象并设置线程组名称
------------------------------------------------------------------------------------------------------------
public class ThreadGroupDemo {
public static void main(String[] args) {
method() ;
method2();
}
//自己设置线程组名称
private static void method2() {
//public ThreadGroup(String name):构造线程组对象并设置线程组名称
ThreadGroup tg = new ThreadGroup("newThreadGroup") ;
//创建Thread类线程对象
Thread t1 = new Thread(tg, "t1线程") ;
Thread t2 = new Thread(tg, "t2线程") ;
String name1 = t1.getThreadGroup().getName() ;
String name2 = t2.getThreadGroup().getName() ;
System.out.println(name1);
System.out.println(name2);
}
//默认的子线程的线程组名称:在主线程中就是main
private static void method() {
//创建线程类对象
ThreadDemo td1 = new ThreadDemo() ;
ThreadDemo td2 = new ThreadDemo() ;
//获取线程组对象
//public final ThreadGroup getThreadGroup()
ThreadGroup tg1 = td1.getThreadGroup() ;
ThreadGroup tg2 = td2.getThreadGroup() ;
//获取该线程所属的线程组名称
//public final String getName()
String name1 = tg1.getName() ;
String name2 = tg2.getName();
System.out.println("name1:"+name1+",name2:"+name2);
}
}
四.线程池(重点)(ExceutorService:接口)
1)概念:
特点:
线程池:产生一个固定的可重用的线程数,线程对象创建完毕并且执行完毕,不会被GC回收掉,而是将该线程对象再次归还到线程池中,等待下一次重复利用
弊端:
使用成本要比传统的开启子线程成本大
2)ExecutorService:接口 线程池:如何实例化(不能直接实例化,所以提供工程类:Executors)
*public static ExecutorService newFixedThreadPool(int nThreads):创建固定的可重用的线程数的线程池!
*ExecutorService 的API:
提交异步任务:
<T> Future<T> submit(Callable<T> task)
Callable:异步任务接口 :
有一个抽象方法:V call() throws Exception:具体计算结果值
<T> Future<T> submit(Runnable task,T result)
*void shutdown() :将之前提交的异步任务按照顺序关闭!
3)使用线程池步骤:
*创建线程池对象:
ExecutorService threadPool = Executors.newFixedThreadPool(2) ; //线程池中有两个线程
*使用线程池提交异步任务:2个 (一个异步任务就是一个线程)
threadPool.submit(new MyRunnable()) ;
threadPool.submit(new MyRunnable()) ;
*关闭线程池里面的异步任务
threadPool.shutdown();
五.定时器:Timer类 和 定时任务:TimerTask类
1)Timer类
1)概念:定时工具,重复执行或者执行一次任务
2)方法:
*构造方法:
Timer():空惨构造,产生一个计时器
*成员方法:
public void schedule(TimerTask task,Date time):在指定的时间计划执行指定的任务。如果时间是过去的,任务将被安排立即执行。
public void schedule(TimerTask task, Date time):在指定的时间内(Date日期格式- getTime()--->long格式),执行一次任务
public void schedule(TimerTask task,long delay, long period):经过delay这个毫秒数后,在每经过period毫秒数重复执行TimerTask任务
public void cancel():取消计时器产生的定时任务
2)需求:
在某个时间点,三天后将某个文件夹中的所有带后缀.java文件全部删除
2)TimerTask类
1)概念:
由计时器安排一次执行或者重复执行定时任务
2)方法:
public abstract void run():计时器执行的任务
六.面向对象设计原则
1)单一职责原则
其实就是开发人员经常说的”高内聚,低耦合”也就是说,每个类应该只有一个职责,对外只能提供一种功能,而引起类变化的原因应该只有一个。在设计模式中,所有的设计模式都遵循这一原则
2)开闭原则
开闭原则核心思想是:一个对象对扩展开放,对修改关闭。其实开闭原则的意思就是:对类的改动是通过增加代码进行的,而不是修改现有代码。也就是说软件开发人员一旦写出了可以运行的代码,就不应该去改动它,而是要保证它能一直运行下去,如何能够做到这一点呢?这就需要借助于抽象和多态,即把可能变化的内容抽象出来,从而使抽象的部分是相对稳定的,而具体的实现则是可以改变和扩展的。
3)里氏替换原则
里氏替换原则核心思想:在任何父类出现的地方都可以用它的子类来替代。其实就是说:同一个继承体系中的对象应该有共同的行为特征。
4)依赖注入原则
依赖注入原则核心思想:要依赖于抽象,不要依赖于具体实现。其实就是说:在应用程序中,所有的类如果使用或依赖于其他的类,则应该依赖这些其他类的抽象类,而不是这些其他类的具体类。为了实现这一原则,就要求我们在编程的时候针对抽象类或者接口编程,而不是针对具体实现编程
5)接口分离原则
接口分离原则核心思想:不应该强迫程序依赖它们不需要使用的方法。其实就是说:一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一个接口中
6)迪米特原则。
迪米特原则核心思想:一个对象应当对其他对象尽可能少的了解其实就是说:降低各个对象之间的耦合,提高系统的可维护性。在模块之间应该只通过接口编程,而不理会模块的内部工作原理,它可以使各个模块耦合度降到最低,促进软件的复用
七.Java常见的设计模式
1)概述
1)概述:
设计模式(Designpattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式不是一种方法和技术,而是一种思想设计模式和具体的语言无关,学习设计模式就是要建立面向对象的思想,尽可能的面向接口编程,低耦合,高内聚,使设计的程序可复用学习设计模式能够促进对面向对象思想的理解,反之亦然。它们相辅相成
2)设计模式要素和分类:
*名字必须有一个简单,有意义的名字问题描述在何时使用模式解决方案描述设计的组成部分以及如何解决问题效果描述模式的效果以及优缺点
*分类:
创建型模式 对象的创建
结构型模式 对象的组成(结构)
行为型模式 对象的行为
3)分类:
创建型模式:简单工厂模式,工厂方法模式,抽象工厂模式,建造者模式,原型模式,单例模式。(6个)
结构型模式:外观模式、适配器模式、代理模式、装饰模式、桥接模式、组合模式、享元模式。(7个)
行为型模式:模版方法模式、观察者模式、状态模式、职责链模式、命令模式、访问者模式、策略模式、备忘录模式、迭代器模式、解释器模式(10个)
2)创建型模式(对象的创建)(重点掌握):###简单工厂
#简单工厂:又称为"静态工厂方法模式",他定义个具体的工厂类负责创建一些类的实例
*优点:
客户端不需要负责对象的创建,从而明确了各个类的职责
*缺点:
这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护
/*//代码示例以及分析
动物抽象类:publicabstractAnimal { publicabstractvoideat(); }
具体狗类:publicclassDogextendsAnimal {}
具体猫类:publicclassCatextendsAnimal {}
开始,在测试类中每个具体的内容自己创建对象,但是,创建对象的工作如果比较麻烦,就需要有人专门做这个事情,所以就知道了一个专门的类来创建对象。
*/
//动物工厂类
public class AnimalFactory {
//私有化:外界不能创建该工厂类对象
private AnimalFactory() {}
//提供功能
public static Animal createAnimal(String type) {
if("dog".equals(type)) {
return new Dog() ; //Animal a = new Dog() ; //多态
}else if("cat".equals(type)) {
return new Cat() ;
}else if("pig".equals(type)) {
return new Pig() ;
}else {
return null ;
}
}
}
-----------------------------------------------------------------------------------------
//如果有新的类增加,在工厂类提供具体的静态功能创建该具体类的实例
public class PatternDemo {
public static void main(String[] args) {
Animal a = AnimalFactory.createAnimal("cat") ;
a.eat();
a.sleep();
//加上判断,使得程序合理
a=AnimalFactory.createAnimal("pig");
if (a!=null) {
a.eat();
} else {
System.out.println("对不起,暂时不提供这种动物");
}
}
}
-----------------------------------------------------------------------------------------
public class Animal { //抽象动物类
public void eat() {
System.out.println("eat");
}
public void sleep() {
System.out.println("sleep");
}
}
-----------------------------------------------------------------------------------------
public class Cat extends Animal {//猫类,继承与抽象动物类
@Override
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void sleep() {
System.out.println("猫趴着睡");
}
}
3)工厂方法模式
#工厂方法模式:工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
*优点:
客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性
*缺点:
需要额外的编写代码,增加了工作量
/*//代码示例以及分析
动物抽象类:publicabstractAnimal { publicabstractvoideat(); }
工厂接口:publicinterfaceFactory {publicabstractAnimalcreateAnimal();}
具体狗类:publicclassDogextendsAnimal {}
具体猫类:publicclassCatextendsAnimal {}
开始,在测试类中每个具体的内容自己创建对象,但是,创建对象的工作如果比较麻烦,就需要有人专门做这个事情,所以就知道了一个专门的类来创建对象。发现每次修改代码太麻烦,用工厂方法改进,针对每一个具体的实现提供一个具体工厂。
*/
------------------------------------------------------------------------------------------------------------
public interface Factory {//工厂接口
//创建动物实例
public abstract Animal crateAnimal() ; //返回值是一个动物类型
}
------------------------------------------------------------------------------------------------------------
public abstract class Animal {//抽象动物类
public abstract void eat();//声明抽象方法,
}
------------------------------------------------------------------------------------------------------------
//每一个具体类都对应一个工厂类
public class CatFactory implements Factory {
@Override
public Animal crateAnimal() { //方法返回值是一个抽象类,需要返回该抽象类的子类对象!
return new Cat();
}
}
public class DogFactory implements Factory {
@Override
public Animal createAnimal() {
return new Dog();
}
}
------------------------------------------------------------------------------------------------------------
//具体类
public class Cat extends Animal {
@Overridepublicvoideat() {
System.out.println("猫吃鱼");
}
}
public class Dog extends Animal {
@Overridepublicvoideat() {
System.out.println("狗吃肉");
}
}
------------------------------------------------------------------------------------------------------------
//主测试类
publicclassAnimalDemo {
publicstaticvoidmain(String[] args) {
// 需求:我要买只狗
Factory f = new DogFactory();
Animala = f.createAnimal();
a.eat();
System.out.println("-------");
//需求:我要买只猫
f = new CatFactory();
a = f.createAnimal();
a.eat();
}
}
4)单例设计模式
#单例设计模式(重点掌握):单例模式就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供。
*优点:
在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
*缺点:
没有抽象层,因此扩展很难。职责过重,在一定程序上违背了单一职责
*单例设计模式的两种形式:
饿汉式(不会出现问题单例)
格式:
**构造方法私有化
**在当前类的成员位置提供该类的实例:静态实例变量
**该类中静态的公共的成员方法获取该类的实例!
**在加载某个类的时候,就已经创建该类的对象,自动创建,对外公共访问方法来获取该类实例;在内存中始终产生一个对象
------------------------------------------------------------------------------------------------------------
public class PatternDemo {
public static void main(String[] args) {
//测试
Student s1 = Student.getStudent() ;
Student s2 = Student.getStudent() ;
System.out.println(s1==s2);//一定true
}
}
------------------------------------------------------------------------------------------------------------
//学生类
public class Student {
private static Student s = new Student();
private Student() {}
//外界获取当前类的实例,静态的公共方法获取
public static Student getStudent() {
return s ;
}
}
------------------------------------------------------------------------------------------------------------
懒汉式(可能出现问题的单例模式)
懒加载(延迟加载)
线程安全问题
是否多线程环境是
是否有共享数据是
是否有多条语句操作共享数据是
格式:
**无参构造方法私有化
**在成员位置声明该类的变量,并且私有化
**提供一个对外的公共访问方法,获取该类实例(需用判断如果为null,才去创建该类对象)
------------------------------------------------------------------------------------------------------------
public class PatternDemo {
public static void main(String[] args) {
//测试
Teacher s1 = Teacher.getTeacher() ;
Teacher s2 = Teacher.getTeacher() ;
System.out.println(s1==s2);//有可能true,有可能false
}
}
------------------------------------------------------------------------------------------------------------
//老师类
public class Teacher {
private static Teacher t = null ;
private Teacher() {}
//静态的同步方法的锁对象:Teacher.class
public synchronized static Teacher getTeacher() {
if(t == null) { //判断如果内存中没有Teacher对象,才去创建对象
t = new Teacher() ;
}
return t ;
}
}
八.File类
1)基本概念以及功能
1)概念:抽象表示的文件和目录的路径名
2)功能:
*构造方法:
File(File parent, String child):参数1:描述文件或者文件夹的File对象,参数2:描述文件或者文件夹的子文件
File(String pathname) :直接描述一个具体的路径 (推荐)举例:File("E:\\demo\\a.txt")
File(String parent, String child) :
*功能:
**创建:
创建文件:
public boolean createNewFile():文件不存在的时候,创建一个新的空文件throws IOException
创建文件夹:
public boolean mkdir():创建一个空文件夹 ;返回值,true,创建成功;false,已经存在了该文件夹
public boolean mkdirs():创建文件夹:如果父文件夹不存在,自动创建!
**获取:
public String getAbsolutePath():获取当前File所表示的绝对路径
public String getPath() :获取当前File相对路劲
public String getName() :获取当前File所表示的文件或者文件夹的名称
public long length() :获取文件长度(存储内容)
public long lastModified() :获取最后一次修改File的时间毫秒值
**删除:
public boolean delete():删除抽象路径表示形式的文件或者文件夹如果不带盘符:默认在当前项目下创建文件/文件夹或者删 除文件/文件夹
注意事项: delete()方法删除文件夹,文件夹必须是空目录(一次删除一个文件夹)
**判断:
public boolean isDirectory() :判断是否是文件夹
public boolean isFile() :判断是否是文件
public boolean exists() :判断当前表示的文件夹或者文件是否存在
public boolean canRead() :文件是否可读
public boolean canWrite() :文件是否可写
public boolean isHidden() :判断文件以及文件夹是否隐藏
*基本功能:
public boolean renameTo(File dest):对当前文件所表示的抽象路径重命名
注意:如果重命名前和重名名后的两个抽象路径形式表示一样,只是重命名!如果重命名前后两个抽象路径的形式不一样,不仅仅是重 命名,还将文件进行剪切!
public String getAbsolutePath():
*高级功能:
**获取:
public String[] list():获取当前某个File所表示目录下面的所有的文件以及文件夹的字符串数组
public File[] listFiles():获取当前某个File所表示目录下面的所有的文件以及文件夹的File组
2)应用
1)需求:获取D盘下所有带后缀名为".jpg"的文件,输出这些文件名称代码示例:
*第一种
/*分析:
*表示D盘的抽象路径形式 File("d:\\")
*public File[] listFiles():获取当前D盘下所有的文件夹以及文件的File数组
*非空判断
**如果不为null,遍历File数组,获取到每一个File对象
**加入判断:如果File表示的路径形式文件的话是文件,(获取文件的名称)endsWith(".jpg");再次判断是否是 以".jpg"结尾的文件符号条件输出即可!
代码:
*/
------------------------------------------------------------------------------------------------------------
public class FileTest {
public static void main(String[] args) {
//1)表示D盘的抽象路径形式 File("d:\\")
File file = new File("d:\\") ;
//2)public File[] listFiles():获取当前D盘下所有的文件夹以及文件的File数组
File[] fileArray = file.listFiles() ;
//3)判断
if(fileArray !=null) {
//遍历File数组
for(File f : fileArray) {
//获取到了每一个File对象
//加入判断:判断File对象是否描述是一个文件
if(f.isFile()) {
//是文件
//判断是否以.jpg结尾
if(f.getName().endsWith(".jpg")) {
System.out.println(f.getName());
}
}
}
}
}
}
------------------------------------------------------------------------------------------------------------
*第二种
/*
* 需求:获取D盘下所有带后缀名为".jpg"的文件,输出这些文件名称
* 方式2:使用FilenameFilter
FileTest类中的方式1,可以用,有没有一种方式,在file对象获取的时候,就已经拿到了文件呢?
FilenameFile:文件名称过滤器 (接口)
public boolean accept(File dir,String name):判断当前是否将该name文件名称添加到文件列表中,
返回是true,将该文件名称添加文件列表中,否则不添加!
File类的功能:
public String[] list(FilenameFilter filter)
public File[] listFiles(FilenameFilter filter)(推荐)
形式参数是一个接口类型,需要改接口的子实现类对象/接口的匿名内部类!
* */
------------------------------------------------------------------------------------------------------------
public class FileTest2 {
public static void main(String[] args) {
//表示D盘
File file = new File("D:\\");
//2)public File[] listFiles(FilenameFilter filter)(推荐):获取文件名称列表的File数组
File[] fileArray = file.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
//加入我们的逻辑判断
File file = new File(dir,name) ;
//判断如果是文件还有判断是否以".jpg",结尾
return (file.isFile() && file.getName().endsWith(".jpg")) ;
}
}) ;
//遍历
for(File f:fileArray) {
if(f!=null) {
System.out.println(f.getName());
}
}
}
}
------------------------------------------------------------------------------------------------------------
2)需求:递归删除带内容的目录
*假设:在当前项目下有demo目录
*分析:
<1>首先使用File对象表示demo路径的抽象形式:File file = new File("demo");
<2>定义一个删除目录的方法: delete(File srcFolder)
<3>在当前方法中获取当前demo目录下的所有的文件以及文件夹的File数组:public File[] listFiles()
<4>非空判断如果当前File数组对象不为空
<5>遍历File数组,获取到每一个File对象
*判断File对象是否是文件夹
是文件夹,回到3)步骤,进行执行删除目录的方法
不是文件夹,输出并直接获取文件的名称以及删除文件 *在依次删除目录,删除demo目录,输出目录名称
*代码实现:
------------------------------------------------------------------------------------------------------------
public class DiGuiTest3 {
public static void main(String[] args) {
//1)首先使用File对象表示demo路径的抽象形式
File srcFolder = new File("demo") ;
//2)定义一个删除目录的方法
delete(srcFolder) ;
}
//递归删除的方法
private static void delete(File srcFolder) {
//获取当前demo目录下的所有的文件以及文件夹的File数组
File[] fileArray = srcFolder.listFiles() ;
//非空判断
if(fileArray!=null) {
//遍历
for(File file :fileArray) {
//获取到每一个File对象
//判断File对象是否是文件夹
if(file.isDirectory()) {
//回到2)步骤,进行执行删除目录的方法
delete(file);
}else {
//是文件,获取文件名称以及删除文件
System.out.println(file.getName()+"---"+file.delete());
}
}
//删除文件夹
System.out.println(srcFolder.getName()+"---"+srcFolder.delete());
}
}
}
九.方法递归
1)概念:方法调用方法的一种现象
2)特点:
*定义个方法
*这个方法要符合结束条件
*还要符合一定规律
3)代码示例
*需求:
有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问第二十个月的兔子对 数为多少?
*规律:
第一个月:1
第二个月:1
第三个月:2
第四个月:3
第五个月:5
第六个月:8
...
...
...
第一个月和第二个月是已知的数据(都是1)
从第三个月开始,每个月的兔子对数等与前两个相邻月的兔子对数之和!
方式2:变量的变化(变量赋值)
使用a,b 看成相邻两个月的兔子对象的数据
第一个(两个月)相邻的数据: 第一个月,第二个月
a = 1 ,b = 1
第二个相邻的数据:第二个月,第三个月
a = 1,b=2
第二个相邻的数据:第三个月,第四个月
a =2 ,b = 3
第三个相邻的数据:第四个月,第五个月
a = 3 , b = 5
规律:
下一次的b是上一次的a+b
下一次a是上一次的b
*代码:
public class DiGuiTest2 {
public static void main(String[] args) {
System.out.println("第二十个月的兔子的对数为:"+getRabbit(20));
}
/*
* n表示第几个月
* */
private static int getRabbit(int n) {
if(n==1 || n==2) { //第一个月和第二月都是1对
return 1 ;//1对
}else {
///从第三个月开始,每个月的兔子对数等与前两个相邻月的兔子对数之和!
return getRabbit(n-1)+ getRabbit(n-2) ;
}
}
}
十.IO流
1)字节流
1>文件字节输入流
*字节输入流:InputStream
*文件字节输入流:FileInputStream类(InputStream抽象类的子类)
功能:
public FileInputStream(File file):将指定File对象作为参数传递构造出文件字节输入流对象
public FileInputStream(String pathname):将指定的文件的路径作为参数传递创建文件字节输入流对象(推荐)
public int read():一个读一个字节
public int read(byte[] b):一次读一个字节数组
*字节输入流的读操作:
两种方式:
------------------------------------------------------------------------------------------------------------
**一次读一个字节
------------------------------------------------------------------------------------------------------------
public class FileInputStreamDemo{
public static void main(String[] args) throws IOException {
//创建一个文件字节输入流对象
FileInputStream fis = new FileInputStream("fis2.txt");
int by = 0 ;//没有开始的读的时候字节为0
while((by = fis.read())!= -1){
System.out.print((char)by);
//中文编码格式:GBK 一个中文对应两个字节
}
//3)关闭资源
fis.close();
}
}
------------------------------------------------------------------------------------------------------------
**一次读一个字节数组
------------------------------------------------------------------------------------------------------------
public class FileInputStreamDemo{
public static void main(String[] args) throws IOException {
//创建一个文件字节输入流对象
FileInputStream fis = new FileInputStream("fis2.txt");
//一般情况:字节数组的缓冲区的长度:都是1024或者1024的整数倍
//(防止这里出现长度不够读取一行,出现后面的空白的)
byte[] buffer = new byte[1024] ; //字节缓冲区
//public int read(byte[] ):实际读取的字节数长度 (最终的字节总数)
int len = 0 ;
//结束条件:-1:没有内容了
//将赋值,判断,读取操作写在一块
while((len=fis.read(buffer))!=-1) {
//String(byte[] bytes,int offerset,int length) :展示实际内容
//System.out.println(new String(buffer)) ;
System.out.println(new String(buffer,0,len)); //每次展示内容:从角标0开始展示实际的字节长度
}
//释放资源
fis.close();
}
}
2>文件字节输出流
*字节输出流:OutputStream(抽象类)
*文件字节输出流:FileOutputStream类(OutputStream抽象类的子类)
功能:
FileOutputStream(File file) :将指定File对象作为参数传递构造出文件字节输出流对象
FileOutputStream(String pathname) :将指定的文件的路径作为参数传递创建文件字节输出流对象(推荐)
FileOutputStream(File file,boolean append):参数1:File对象表示的路径抽象形式;参数2:如果true,在文件的字节末尾追加数据
public void write(int b) :写入一个字节数据
public void write(byte[] b) :写一个字节数组
public void write(byte[] b, int off, int len):写一个字节数组的一部分
*字节输出流的写操作:
两种方式:
----------------------------------------------------------------------------------------------------
**一次写一个字节
----------------------------------------------------------------------------------------------------
public class FileOutputStreamDemo2 {
public static void main(String[] args) throws IOException {
//创建文件字节输出流对象
FileOutputStream fos = new FileOutputStream("fos2.txt") ;
//public void write(int b) :写入一个字节数据
fos.write(97); //写入的字节--->底层换算成二进制数据---->使用记事本打开--->对应的ASCII码表
//释放资源
fos.close();
}
}
----------------------------------------------------------------------------------------------------
**一次写一个字节数组和写一个字节数组的一部分
----------------------------------------------------------------------------------------------------
public class FileOutputStreamDemo2 {
public static void main(String[] args) throws IOException {
//创建文件字节输出流对象
FileOutputStream fos = new FileOutputStream("fos2.txt") ;
//public void write(byte[] b) :写一个字节数组
byte[] bytes = {97,98,99,100,101} ;
//fos.write(bytes);/写入整个字节数组
//public void write(byte[] b, int off, int len):
fos.write(bytes, 1, 2); //从指定位置写,写len个
//释放资源
fos.close();
}
}
----------------------------------------------------------------------------------------------------
3>综合应用
*使用字节输入流,字节数出流进行文件复制操作
*分析:
**源文件:使用文件字节输入流读取数据:FileInputStream 读取D:\\DiGuiTest3.java
**目标数据:使用文件字节输出流写数据:FileOutputStream 将上面的读取的内容输出Copy.java文件中
**读写复制操作
两种方式:
一次读取一个字节
一次读取一个字节数组
*代码实现
----------------------------------------------------------------------------------------------------
public class CopyFileTest {
public static void main(String[] args) throws IOException {
method("D:\\DiGuiTest3.java","Copy.java") ;
method2("D:\\DiGuiTest3.java","Copy.java") ;
}
//一次读取一个字节数组
private static void method2(String srcString, String destString) throws IOException {
//创建一个文件字节输入流对象:读取源文件的内容
FileInputStream fis = new FileInputStream(srcString) ;
//创建一个文件字节输出流对象:将读取的内容写入到目标文件中
FileOutputStream fos = new FileOutputStream(destString) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=fis.read(bytes))!=-1) {
//读取实际长度,写入实际长度
fos.write(bytes, 0, len);
}
//释放资源
fos.close();
fis.close();
}
//复制操作:一次读取一个字节
private static void method(String srcString, String destString) throws IOException {
//创建一个文件字节输入流对象:读取源文件的内容
FileInputStream fis = new FileInputStream(srcString) ;
//创建一个文件字节输出流对象:将读取的内容写入到目标文件中
FileOutputStream fos = new FileOutputStream(destString) ;
//读写复制操作
int by = 0 ;
while((by=fis.read())!=-1) {
//第一个字节,写一个字节
fos.write((char)by);
}
//释放资源
fos.close();
fis.close();
}
}
----------------------------------------------------------------------------------------------------
2)字节缓冲流
1)字节缓冲输入流
*构造方法:
BufferedInputStream(InputStream in) :构造一个字符缓冲输入流对象,默认的缓冲区大小(足够大)
BufferedInputStream(InputStream in, int size) :指定一个缓冲区大小,构造一个缓冲输入流对象!
*成员方法:
读取数据:
一次读取一个字节:public int read()
一次读取一个字节数组:public int read(bytes[] bytes)
2)字节缓冲输出流:仅仅是提供一个缓冲区而已,不能直接操作文件!
构造方法:
public BufferedOutputStream(OutputStream out):构造一个缓冲区流对象,默认的缓冲区大小(足够大了)
public BufferedOutputStream(OutputStream out,int size):构造一个缓冲区流对象,指定缓冲区大小(不用)
成员方法:
void write(byte[] b, int off, int len) :写字节数组的一部分
void write(byte[] b) :写一个字节数组
void write(int b) :写一个字节
##注意:该类流针对文件的操作,还是需要去使用最基本的字节输出流操作
3)使用格式:
<1>匿名对象格式
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bos.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt")) ;
<2>创建对象格式
FileInputStream fin = new FileInputStream("bos.txt");
BufferedInputStream bis = new BufferedInputStream(fin);
FileOutputStream fos = new FileOutputStream("bos.txt");
BufferedOutputStream bos = new BufferedOutputStream("bos.txt");
3)字符流
1>文件字符输入流
*字符输入流:Reader(抽象类)
*文件字符输入流:FileReader类
**功能:只能读取普通文本文件,读取文本内容比较方便,快捷.
构造方法:
public FileReader(File file)throws FileNotFoundException:创建一个新的 FileReader,给予 File读。
public FileReader(String fileName)throws FileNotFoundException:创建一个新的 FileReader,给定要读取的文件的名称
普通方法:
public int read() throws IOException:读取单个字符。
public int read(char[] cbuf,int offset,int length)throws IOException:将字符读入一个数组的一部分。
public int read(char[] cbuf):将字符读入一个数组
*使用字符输入流读取文件
两种方式:
----------------------------------------------------------------------------------------------------
**一次读一个字符
----------------------------------------------------------------------------------------------------
public class FileReaderDemo{
public static void main(String[] args) {
//创建一个文件字节输入流对象
FileReader fis = null;
try {
fis = new FileReader("E:\\Eclipse-JavaProject\\day-test\\fis2.txt");
char[] chars = new char[4];
fis.read(chars);
for (char c : chars) {
System.out.print(c);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//3)关闭资源
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
----------------------------------------------------------------------------------------------------
**一次读一个字符数组
----------------------------------------------------------------------------------------------------
public class FileReaderDemo{
public static void main(String[] args) {
//创建一个文件字节输入流对象
FileReader fis = null;
try {
fis = new FileReader("E:\\Eclipse-JavaProject\\day-test\\fis2.txt");
char[] chars = new char[4];
int count = 0;
while((count=fis.read(chars))!=-1) {
System.out.println(new String(chars,0,count));
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//3)关闭资源
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
----------------------------------------------------------------------------------------------------
2>文件字符输出流
*字符输出流:Writer
**文件字符输出流:FileWriter类
功能:只能写入普通文本文件,写入文本内容比较方便,快捷.
构造方法:
public FileWriter(File file)throws FileNotFoundException:构建了一个文件对象FileWriter对象
public FileWriter(String fileName)throws FileNotFoundException:构造给定文件名的FileWriter对象
public FileWriter(File file,boolean append)throws IOException:构建了一个文件对象FileWriter对象。如果 第二 true,然后字节将被写入到文件的末尾而不是开头。
public FileWriter(String fileName,boolean append)throws IOException:构造FileWriter对象给出一个文件名 与一个布尔值,指示是否附加写入的数据。
普通方法:
public void write(int c)throws IOExceptio:写一个字符。
public void write(char[] cbuf):写入一个字符数组
public void write(char[] cbuf,int off,int len)throws IOException:写入一个字符数组的一部分。
public void write(String str,int off,int len)throws IOException:写入字符串的一部分
*使用字符输出流写入文件
两种方式:
----------------------------------------------------------------------------------------------------
**一次写一个字节
----------------------------------------------------------------------------------------------------
public class FileWriterDemo {
public static void main(String[] args){
//创建文件字符输出流对象
FileWriter fos = null;
try {
fos = new FileWriter("fos2.txt") ;
fos.write("的");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
----------------------------------------------------------------------------------------------------
**一次写一个字节数组和写一个字节数组的一部分
----------------------------------------------------------------------------------------------------
public class FileWriterDemo {
public static void main(String[] args){
//创建文件字符输出流对象
FileWriter fos = null;
try {
fos = new FileWriter("fos2.txt") ;
char[] chars = {'中','国','人','解','放','军'};
fos.write(chars);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
3>应用
*使用字符输入流,字符数出流进行文件复制操作
*分析:
**源文件:使用文件字符输入流读取数据:FileReader 读取D:\\DiGuiTest3.java
**目标数据:使用文件字符输出流写数据:FileWriter 将上面的读取的内容输出Copy.java文件中
**读写复制操作
两种方式:
一次读取一个字符
一次读取一个字符数组
*代码实现
----------------------------------------------------------------------------------------------------
package com.day8_18;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterDemo {
public static void main(String[] args) throws IOException {
method("E:\\Eclipse-JavaProject\\day-test\\src\\com\\day8_16\\ThreadDemo.java","Copy.java") ;
//method2("E:\\Eclipse-JavaProject\\day-test\\src\\com\\day8_16\\ThreadDemo.java","Copy.java") ;
}
//一次读一个字符数组
private static void method2(String srcString, String destString){
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader(srcString);
fw = new FileWriter(destString) ;
char[] bytes = new char[1024] ;
int len = 0 ;
while((len=fr.read(bytes))!=-1) {
//读取实际长度,写入实际长度
fw.write(bytes, 0, len);
}
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//释放资源
try {
fr.close();
fw.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//复制操作:一次读取一个字符
private static void method(String srcString, String destString) throws IOException {
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader(srcString);
fw = new FileWriter(destString) ;
int count = 0;
while((count=fr.read())!=-1) {
//读取实际长度,写入实际长度
fw.write(count);
}
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
fr.close();
fw.close();
}
}
}
----------------------------------------------------------------------------------------------------
4)字符缓冲流
1)BufferedWriter:字符输出缓冲流
构造方法:
BufferedWriter(Writer out) :创建一个字符缓冲输出流对象 默认的缓冲区大小(默认值足够大了)
BufferedWriter(Writer out, int sz):创建一个字符缓冲输出流对象,指定的缓冲区大小
成员方法:
public void flush():输出管道刷新
write(int ch):写单个字符
write(char[] chs):写字符数组
write(char[] chs,int offerset,int length) :写字符数组的一部分
write(String str) 写字符串
特有功能:
public void newLine()throws IOException :写入行的分隔符
字节流中写入换行符号:windows"\r\n"
2)BufferedReader:字符输入缓冲流
构造方法:
BufferedReader(Reader in) :默认的字符缓冲区输入流对象
BufferedReader(Reader in, int sz) :指定的大小的字符缓冲输入流对象
成员方法:
public int read():读取单个字符
public int read(char[] chs):读取一个字符数组
特有功能:
public String readLine():一次读取一行内容
3)使用格式:与字节缓冲流一样的使用方式
下一篇: 生产者和消费者
推荐阅读