【Java笔记】基础学习笔记汇总(中)
- 【Java笔记】基础学习笔记汇总(中)
- 1. java.lang.Throwable:Java语言中所有错误或异常的父类
- 2. JVM 中断处理异常 的过程
- 3. throw关键字
- 4. Objects类的一个静态方法:requireNonNull
- 5. throws关键字(异常处理方式1)
- 6. try...catch...(异常处理方式2)
- 7. Throwable类中定义3个异常处理的方法
- 8. 多个异常try...catch...的处理方法
- 9. 子父类的异常处理
- 10. 自定义异常类
- 11. 并发与并行
- 12. 线程
- 13. 多线程
- 14. 创建多线程程序的第一种方法:创建Thread类的子类
- 15. 创建多线程程序的第二种方法:实现Runable接口
- 16. 匿名内部类方式实现线程的创建
- 17. 线程安全问题
- 18. 第一种:同步代码块
- 19. 第二种:同步方法
- 20. 第三种:Lock锁
- 21. 线程状态(6种)
- 22. Object类中的 .wait() 和 .notify() 方法
- 23. 进入到Time Waiting(计时等待)的两种方法和唤醒的方法
- 24. 等待唤醒机制
- 25. 线程池
- 26. 线程池工厂类:java.util.concurrent.Executors
- 27. 线程池的使用步骤:
- 28. JDK1.8之后的新特性:Lambda表达式
- 29. java.io.File类
- 30. 绝对路径和相对路径
- 31. File类的常用方法 第一类:获取功能
- 32. File类的常用方法 第二类:判断功能
- 33. File类的常用方法 第三类:创建删除功能
- 34. File类遍历文件夹/目录功能
- 35. 补充String类中的两个方法:.endWith()和.toLowerCase()
- 36. java.io.FileFilter和java.io.FilenameFilter两个文件过滤器
- 37. IO流
- 38. java.io.OutputStream和子类java.io.FileOutputStream
- 39. java.io.InputStream和子类java.io.FileInputStream
- 40. java.io.Reader和子类java.io.FileReader
- 41. java.io.Writer和子类java.io.FileWriter
- 42. IO异常的处理
- 43. java.util.Properties集合 extends Hashtable< k,v >
- 44. java.io.BufferedOutputStream字节缓冲输出流 extends OutputStream
- 45. java.io.BufferedInputStream字节缓冲输入流 extends InputStream
- 46. 读取效率最高:缓冲流+缓冲数组 代码示例
- 47. java.io.BufferedWriter字符缓冲输出流 extends Writer
- 48. java.io.BufferedReader字符缓冲输入流 extends Reader
- 49. 字符缓冲输入流和输出流 代码示例
- 50. FileReader读取不同编码方式的文本时遇到的问题
- 51. 转换流java.io.OutputStreamWriter extends Writer
- 52. 转换流java.io.InputStreamReader extends Reader
- 53. 转换流 代码示例
- 54. java.io.ObjectOutputStream对象的序列化流 extends OutputStream
- 55. java.io.ObjectInputStream对象的反序列化流 extends InputStream
- 56. 序列化集合
- 57. transient关键字 和static关键字 修饰的成员变量不能被序列化
- 58. 打印流java.io.PrintStream extends FilterOutputStream extends OutputStream
【Java笔记】基础学习笔记汇总(中)
原本笔记都是手写的,为了之后方便保存和查阅,还是下定决心,把他敲出来正好作为一篇博客的内容。也借这个机会,好好的复习一遍Java基础知识。这是中半部分。
自学材料:黑马程序员Java全套视频
1. java.lang.Throwable:Java语言中所有错误或异常的父类
其下有两个子类:
- java.lang.Error
- java.lang.Exception
2. JVM 中断处理异常 的过程
1)首先,JVM在代码中检测处程序会出现异常,JVM做两件事
- JVM会根据异常产生的原因创建一个异常对象,这个异常对象包含了异常产生的信息,包括 内容、原因、位置
- 在某方法中,若没有异常的处理逻辑(try…catch…),那么JVM就会把异常对象抛出给方法的调用者main方法来处理这个异常
2)main方法接受到这个异常对象,main方法也没有异常的处理逻辑,于是把对象抛出给main方法的调用者JVM处理
3)JVM接收到这个异常对象,做了两件事:
- 把异常对象(内容、原因、位置)以红色的字体打印在控制台上
- JVM会终止当前正在执行的Java程序:中断处理
3. throw关键字
作用:可以使用throw关键字在指定的方法中抛出特定的异常
使用格式:
throw new xxxException(“原因。。”)
注意:
1)throw关键字必须写在方法的内部
2)throw后面的new的对象必须是Exception或Exception的子类对象
3)throw关键字抛出的异常对象,我们必须处理这个异常对象
- 若是RuntimeException或RuntimeException子类对象,我们可以不处理,默认交给JVM处理(打印异常、中断程序)
- 若是编译异常(写代码时报错),我们必须处理这个异常,要么throws,要么try…catch…
4. Objects类的一个静态方法:requireNonNull
public static < T > T requireNonNull(T obj):查看指定的索引对象是不是null
可以用来对于参数进行合法性的判断:
- Objects.requireNonNull(obj)
- Objects.requireNonNull(obj,“原因。。。”)
5. throws关键字(异常处理方式1)
交给JVM处理:中断处理
注意:
1)throws关键字必须写在方法声明处
2)throws后面声明的异常必须是Exception或Exception的子类
3)方法内部如果抛出多个异常对象,那么throws后边必须也声明多个异常
如果抛出多个异常对象有子父类关系,那么直接声明父类异常即可
4)这里throws是用在编译器异常,运行期异常只需throw出来就可以了
6. try…catch…(异常处理方式2)
注意:
1)try中可能会抛出多个异常对象,那么就会使用多个catch来处理这些异常对象
2)如果try中产生异常,那么就会执行catch中的异常处理逻辑,执行完毕catch中处理逻辑,继续执行try…catch…后面的代码
如果try中没有产生异常,那么就不会执行catch中异常处理的逻辑,执行完try中的代码,然后继续执行try…catch…后面的代码
7. Throwable类中定义3个异常处理的方法
1)String getMessage():返回此throwable的简短描述
2)String toString():返回此throwable的详细消息字符串
3)void printStackTrace():JVM打印异常对象,默认此方法,打印的异常信息是最全面的
8. 多个异常try…catch…的处理方法
1)多个异常分别处理
2)多个异常一次性捕获,多次处理:try…catch…catch…
注意:catch里面定义的异常变量,如果有子父类关系,那么子类的异常变量必须写在上面,否则会报错
3)多个异常一次性捕获,一次处理:try…catch…
9. 子父类的异常处理
- 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者父类异常的子类或者不抛出异常
- 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常,此时子类产生该异常,只能捕获处理,不能声明抛出
总结:父类异常是什么样,子类异常是什么样
10. 自定义异常类
格式:
public class xxxException extends Exception或RuntimeException{
添加一个空参数的构造方法
添加一个带异常信息的构造方法
}
注意:
1)自定义异常类一般都是Exception结尾,说明该类是一个异常类
2)自定义异常类,必须继承Exception(编译器异常)或RuntimeException(运行期异常)
11. 并发与并行
- 并发:指两个或多个事件在同一个时间段内发生(交替执行)
- 并行:指两个或多个事件在同一时刻发生(同时执行)
12. 线程
线程属于进程。是进程中的一个执行单元,负责程序的执行
线程调度的策略:分时调度;抢占式调度
主线程:
执行主(main)方法的线程
是单线程程序:Java程序中只有一个主线程
13. 多线程
java程序属于抢占式调度,哪个线程的优先级高,哪个线程优先执行;同一个优先级,随机选择一个执行
多线程的好处:多个线程之间互不影响(在不同的栈空间)
14. 创建多线程程序的第一种方法:创建Thread类的子类
java.lang.Thread
实现步骤:(并发执行)
1)创建一个Thread子类
2)在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么)
3)创建Thread类的子类对象
4)调用Thread类中的 .start() 方法,开启新的线程,执行run方法
Thread类的常用方法
1)获取线程的名称(2种方法)
-
使用Thread类中的方法 .getName():返回线程的名称
-
可以先获取当前正在执行的线程,使用线程中的方法 .getName() 来获取线程的名称
-
- static Thread currentThread():返回当前正在执行的线程对象的引用
2)设置线程的名称(2种方法)
- 使用Thread类中的 .setName(…) 方法
- 创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给线程起一个名字,例如:
public MyThread(String name){
super(name);
}
3)public static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停,毫秒数结束之后,线程继续执行
例如:Thread.sleep(1000);
代码示例:
/*
获取线程的名称:
1.使用Thread类中的方法getName()
String getName() 返回该线程的名称。
2.可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
static Thread currentThread() 返回对当前正在执行的线程对象的引用。
*/
// 定义一个Thread类的子类
public class MyThread extends Thread{
//重写Thread类中的run方法,设置线程任务
@Override
public void run() {
//获取线程名称
//String name = getName();
//System.out.println(name);
//Thread t = Thread.currentThread();
//System.out.println(t);//Thread[Thread-0,5,main]
//String name = t.getName();
//System.out.println(name);
//链式编程
System.out.println(Thread.currentThread().getName());
}
}
package com.itheima.demo01.getName;
/*
线程的名称:
主线程: main
新线程: Thread-0,Thread-1,Thread-2
*/
public class Demo01GetThreadName {
public static void main(String[] args) {
//创建Thread类的子类对象
MyThread mt = new MyThread();
//调用start方法,开启新线程,执行run方法
mt.start();
new MyThread().start();
new MyThread().start();
//链式编程
System.out.println(Thread.currentThread().getName());
}
}
15. 创建多线程程序的第二种方法:实现Runable接口
java.lang.Runable
Runable接口在被实现时,需要在类中必须定义一个称为run的无参数方法
java.lang.Thread类的常用构造方法:
-
Thread()
-
Thread(String name)
-
Thread(Runable target)
-
Thread(Runable target, String name)
实现步骤:
1)创建一个Runable接口的实现类
2)在实现类中重写Runable接口的run方法,设置线程任务
3)创建一个Runable接口的实现类对象
4)创建Thread类对象,构造方法中传递Runable接口的实现类对象
5)调用Thread类中的start方法,开启新的线程执行run方法
实现Runable接口创建多线程的优势:
1)避免了单继承的局限性。实现Runable接口,还可以继承其他的类,实现其他接口
2)把设置线程任务和开启新线程进行了分离。增强了程序的扩展性,降低了程序的耦合性(解耦)
代码示例:
package com.itheima.demo04.Runnable;
//1.创建一个Runnable接口的实现类
public class RunnableImpl2 implements Runnable{
//2.在实现类中重写Runnable接口的run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println("HelloWorld"+i);
}
}
}
public class Demo01Runnable {
public static void main(String[] args) {
//3.创建一个Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
//Thread t = new Thread(run);//打印线程名称
Thread t = new Thread(new RunnableImpl2());//打印HelloWorld
//5.调用Thread类中的start方法,开启新的线程执行run方法
t.start();
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
16. 匿名内部类方式实现线程的创建
匿名内部类的最终产物:子类/实现类对象,并且没有类名字
格式:
new 父类/接口 (){
重写父类/接口 中的方法
}
创建线程例子:
//第一种
new Thread(){
//重写run方法
}.start();
//第二种
Runable r = new Runable(){
//重写run方法
};
new Thread(r).start();
//第二种可简化为
new Thread(new Runable(){
//重写run方法
}).start();
17. 线程安全问题
多线程访问共享数据,会产生线程安全问题,解决这一问题的方法:线程同步
有三种:1)同步代码块;2)同步方法;3)Lock锁机制
18. 第一种:同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码块
}
注意:
1)同步代码中的锁对象,可以使用任意对象
2)但是必须保证多个线程使用的锁对象是同一个
3)锁对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行
锁对象,叫作同步锁,或对象锁,或对象监视器
同步中的线程,没有执行完毕,不会释放锁;同步外的线程没有锁,进不去同步代码块
用这种同步代码块的方式存在的问题:
程序频繁地判断锁、获取锁、释放锁,程序的效率会降低
19. 第二种:同步方法
先把可能产生线程安全问题的代码抽取出来放到一个方法中,在方法上添加synchronized修饰符
格式:
修饰符 synchronized 返回数据类型 方法名(参数列表){
可能会产生线程安全问题的代码
}
同步方法的同步锁:
- 对于非static同步方法,同步锁就是this。
- 对于static同步方法,我们使用当前方法所在类的字节码对象(类名.class)。
20. 第三种:Lock锁
java.util.concurrent.locks.Lock 接口提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
- public void lock():加同步锁。
- public void unlock():释放同步锁
java.util.concurrent.locks.ReentrantLock 实现了Lock接口
这种方法的使用步骤:
1)在成员变量位置,创建一个ReetrantLock对象
2)在可能出现安全问题的代码前调用lock()方法获取锁
3)在可能出现安全问题的代码后调用unlock()方法释放锁(最好是写在try…catch…后的finally代码块中)
代码示例:
public class Ticket implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
lock.lock();
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket‐‐);
}
lock.unlock();
}
}
}
21. 线程状态(6种)
注意:
1)阻塞状态:具有cpu的执行资格,等待cpu执行空间
2)休眠状态(计时等待):放弃cpu的执行资格,cpu空闲也不执行
3)无限等待Wating状态在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的Object.notify()方法 或 Object.notifyAll()方法。
其实waiting状态并不是一个线程的操作,它体现的是多个线程间的通信,可以理解为多个线程之间的协作关系,多个线程会争取锁,同时相互之间又存在协作关系。
当多个线程协作时,比如A,B线程,如果A线程在Runnable(可运行)状态中调用了wait()方法那么A线程就进入了Waiting(无限等待)状态,同时失去了同步锁。假如这个时候B线程获取到了同步锁,在运行状态中调用了notify()方法,那么就会将无限等待的A线程唤醒。注意是唤醒,如果获取到锁对象,那么A线程唤醒后就进入Runnable(可运行)状态;如果没有获取锁对象,那么就进入到Blocked(锁阻塞状态)。
完整的线程状态变化图:
22. Object类中的 .wait() 和 .notify() 方法
- void wait()
在其他线程调用此对象的 .notify() 方法前,导致当前线程无限等待
- void notify()
唤醒在此对象监视器上无限等待的单个线程
23. 进入到Time Waiting(计时等待)的两种方法和唤醒的方法
1)使用 .sleep(long m)方法
在毫秒值结束之后,线程睡醒进入到Runable/Blocked状态
2)使用 .wait(long m) 方法
在毫秒值结束之前,也没有被 notify() 方法唤醒,那么毫秒值结束之后,会自动醒来。线程睡醒进入到Runable/Blocked状态
唤醒的方法:
1)void notify():唤醒在此对象监视器上等待的单个线程
2)void notifyAll():唤醒在此对象监视器上等待的所有线程
调用wait和notify方法注意事项:
1)wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对
象调用的wait方法后的线程。
2)wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继
承了Object类的。
3)wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
24. 等待唤醒机制
解决线程之间通信问题
重点:有效地利用资源
25. 线程池
JDK1.5之后,内置了线程池,可以直接使用
合理利用线程池的好处:
1)降低资源消耗。减少了创建和销毁线程的次数
2)提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行
3)提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内
存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)
26. 线程池工厂类:java.util.concurrent.Executors
线程工厂类里面提供了一些静态工厂,生成一些常用的线程池
1)Executors类中 创建线程池的静态方法:
public static ExecutorService newFixedThreadPool(int nThreads):创建一个可重用的固定线路数的线程池
参数:int nThreads:创建线程池中包含的线程数量
返回值:ExecutorService线程池接口的实现类对象
2)获取到线程池ExecutorService 对象,使用线程池对象的方法:
- .submit(Runable task):提交一个Runable任务用于执行
- .shutdown():关闭/销毁线程池
27. 线程池的使用步骤:
1)使用线程池的工厂类Executors里提供的静态方法newFixedThreadPool生成一个指定线程数的线程池
2)创建一个类,实现Runable接口,重写run方法,设置线程任务
3)调用ExecutorService中方法submit(),传递线程任务(Runable实现类),开启线程执行run方法
4)调用ExecutorService中方法shutdown(),销毁线程池(不建议执行)
28. JDK1.8之后的新特性:Lambda表达式
匿名内部类使用示例:
public class Demo04ThreadNameless {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}).start();
}
}
Lambda表达式写法:
public class Demo02LambdaRunnable {
public static void main(String[] args) {
new Thread(() ‐> System.out.println("多线程任务执行!")).start(); // 启动线程
}
}
Lambda表达式的标准格式由三部分组成:
- 一些参数
- 一个箭头 ->
- 一段代码
格式:
(参数类型 参数名称) ‐> { 一些重写方法的代码 }
格式说明:
- ():接口中抽象方法的参数列表,没有参数就空着;有参数就写出来,多个参数使用逗号隔开
- ->:传递的意思,把参数传递给方法体{}
- {}:重写接口的抽象方法的方法体
Lambda表达式的省略规则:
1)(参数列表):括号中的参数列表的数据类型可以省略
2)(参数列表):括号中的参数如果只有一个,那么数据类型和 () 都可以省略
3){一些代码}:如果 { } 中的代码只有一行,无论是否有返回值,都可以省略 { }、return、分号
注意:要省略 { }、return、分号 时,必须一起省略
Lambda的使用前提:
1)使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
无论是JDK内置的Runnable 、Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一
时,才可以使用Lambda。
2)使用Lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
注意:有且仅有一个抽象方法的接口,称为“函数式接口”。
29. java.io.File类
1)File类的4个静态成员变量
- static String pathSeparator:与系统有关的路径分隔符
- 类似于:static char pathSeparatorChar
路径分隔符:windows系统 = 分号“;” linux系统 = 冒号“:”
- static String separator:与系统有关的默认名称分隔符
- 类似于:static char separatorChar
文件名称分隔符:windows = 反斜杠“\” linux系统 = 正斜杠“/”
File类的构造方法(3种)
1)FIle(String pathname)
注意:
- 路径可以是文件结尾,也可以是以文件夹结尾
- 路径可以是相对路径,也可以是绝对路径
- 路径可以是存在的,也可以是不存在的
- 创建File对象,只是把字符串路径封装为File对象,不考虑路径的真假情况
2)File(String parent, String child)
好处:父路径和子路径,可以单独书写,使用起来更灵活;父子路径都可以改变
3)File(File parent, String child)
30. 绝对路径和相对路径
- 绝对路径:一个完整的路径。从盘符开始的路径
- 相对路径:相对于当前项目的根目录的路径
31. File类的常用方法 第一类:获取功能
1)public String getAbsolutePath():返回FIle对象的绝对路径名字符串
2)public String getPath():返回的就是调用构造方法时传入的路径
注意:FIle类的toString()方法源码就是调用getPath()方法
3)public String getName():返回这个FIle对象表示的文件或目录的名称
4)public long length():返回FIle对象表示的文件的大小;以“字节”为单位
注意:1)文件家是没有大小的概念的,不能获取文件夹的大小;2)如果构造方法中给出的路径不存在,返回为 0
32. File类的常用方法 第二类:判断功能
1)public boolean exists():判断路径是否存在
2)public boolean isDirectory():判断构造方法中给定的路径是否为文件夹结尾
3)public boolean isFile():判断给定的路径是否以文件结尾
33. File类的常用方法 第三类:创建删除功能
1)public boolean createNewFile()
当且仅当具有该名称的文件不存在时,才会创建一个新的空文件
创建文件的路径和名称在构造方法中给出
返回值:布尔值
- true:文件不存在,创建文件
- false:文件存在,不会创建
注意:
- 此方法只能创建文件,不能创建文件夹
- 创建文件的路径必须存在,否则会抛出异常
2)创建文件夹
- public boolean mkdir():创建单极文件夹
- public boolean mkdirs():既可以单极也可以多级
创建文件夹的路径和名称在构造方法中给出
返回值:布尔值
- true:文件夹不存在,创建文件夹
- false:文件夹存在,不会创建;构造方法中给出的路径不存在
注意:
- 此方法只能创建文件夹,不能创建文件
3)public boolean delete()
返回值:布尔值
- true:文件/文件夹删除成功
- false:文件夹中有内容,不会删除;构造方法中的路径不存在,不会删除
注意:
- delete方法是在硬盘上删除文件/文件夹,不走回收站
34. File类遍历文件夹/目录功能
1)public String[] list():返回一个String数组,表示该FIle目录下的所有子文件或目录
能看到隐藏文件或文件夹
2)public File[] listFIles():返回一个FIle数组,表示该FIle目录下的所有子文件或目录
能看到隐藏文件或文件夹
注意:
- list和listFIles方法遍历的是构造方法中给出的目录
- 若构造方法中给出的目录的路径不存在,会抛出空指针异常
- 若构造方法中给出的路径不是一个目录,也会抛出空指针异常
35. 补充String类中的两个方法:.endWith()和.toLowerCase()
1).endsWith(…):判断字符串是不是以…为结尾
2).toLowerCase():把字符串转换为小写
36. java.io.FileFilter和java.io.FilenameFilter两个文件过滤器
具体可见手写笔记:第192条内容
java.io.FileFilter是一个接口,是File的过滤器。 该接口的对象可以传递给File类的listFiles(FileFilter)作为参数, 接口中只有一个方法。
boolean accept(File pathname):测试pathname是否应该包含在当前File目录中,符合则返回true。
分析:
1)接口作为参数,需要传递子类对象,重写其中方法。我们选择匿名内部类方式,比较简单。
2)accept方法,参数为File,表示当前File下所有的子文件和子目录。保留住则返回true,过滤掉则返回false。保留规则:
-
要么是.java文件。
-
要么是目录,用于继续遍历。
-
通过过滤器的作用,listFiles(FileFilter)返回的数组元素中,子文件对象都是符合条件的,可以直接打印。
代码实现:
public class DiGuiDemo4 {
public static void main(String[] args) {
File dir = new File("D:\\aaa");
printDir2(dir);
}
public static void printDir2(File dir) {
// 匿名内部类方式,创建过滤器子类对象
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith(".java")||pathname.isDirectory();
}
});
// 循环打印
for (File file : files) {
if (file.isFile()) {
System.out.println("文件名:" + file.getAbsolutePath());
} else {
printDir2(file);
}
}
}
}
37. IO流
1个字符 = 2个字节
1个字节 = 8个二进制位
输入I:把硬盘中的数据,读取到内存中
输出O:把内存中的数据,写入到硬盘中保存
顶级父类:
目前学到的IO流有:
- java.io.OutputStream
-
- java.io.FileOutputStream
-
- java.io.PrintStream
- java.io.BufferedOutputStream
- java.io.ObjectOutputStream
- java.io.InputStream
-
- java.io.FileInputStream
- java.io.BufferedInputStream
- java.io.ObjectInputStream
- java.io.Reader
-
- java.io.InputStreamReader
-
- java.io.FileReader
- java.io.BufferedReader
- java.io.Writer
-
- java.io.OutputStreamWriter
-
- java.io.FileWriter
- java.io.BufferedWriter
- java.util.Properties
38. java.io.OutputStream和子类java.io.FileOutputStream
java.io.OutputStream:字节输出流;这个抽象类,是所有输出字节流所有类的父类
java.io.OutputStream抽象类里定义了输出字节流子类的一些共性的成员方法:
1)public void close():关闭输出流并释放与此流相关联的任何资源;流的关闭原则:先开后关,后开先关。
2)public void flush():刷新此输出流并强制任何缓冲区中的输出字节流被写出
3)public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流
4)public void write(byte[] b, int off, int len):从指定的字节数组写入len字节,从偏移量off开始输出到此输出流
5)public abstract void write(int b):将指定的字节输出流
java.io.FileOutputStream extends OutputStream:文件字节输出流
构造方法:
- public FileOutputStream(File file) :创建向指定的 File对象表示的文件中写入数据的文件输出流
- public FileOutputStream(String name) : 创建向具有指定的名称的文件中写入数据的输出文件流
注意:
当你创建一个流对象时,必须传入一个文件路径。
该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。
文件字节输出流的使用步骤:
1)创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
2)调用FileOutputStream对象的方法write(),把数据写入到文件中
3)释放资源,流使用会占用一定的内存,使用完毕要把内存清空,提高程序的效率
注意:
- 如果写的第一个字节是正数(0-127),那么显示的时候会查询ASCII表
- 如果写的第一个字节是负数,那第一个字节会和第二个字节组合成一个中文显示,查询系统默认的GBK码表
追加写/续写:使用两个参数的构造方法
- FileOutputStream(String name, boolean append)
- FileOutputStream(File file, boolean append)
其中,boolean append:追加写开关
- true:创建对象不会覆盖原文件,继续在文件的末尾写数据
- false:创建一个新文件,覆盖原文件
写出换行符号:
例如:fos.write("\r\n".getBytes());
各个系统中的换行符号:
- Windows系统:\r\n ;
- linux系统:/n ;
- Mac系统:/r;从Mac OS X开始与Linux统一
39. java.io.InputStream和子类java.io.FileInputStream
java.io.InputStream:字节输入流,这个抽象类是字节输入流的所有类的父类
java.io.InputStream抽象类里定义了输入字节流子类的一些共性的成员方法:
1)public abstract int read():从输入流读取下一个字节。读一个字节,读到文件末尾,返回-1
2)public int read(byte[] b):从输入流中读取一定数量的字节,并存储到缓冲区字节数组b中
3)public void close():流的关闭原则:先开后关,后开先关。
java.io.FileInputStream:文件字节输入流
构造方法:
- FileInputStream(String name)
- FileInputStream(File file)
参数:读取文件的数据源
文件字节输入流的使用步骤:
1)创建FileInputStream对象,构造方法中绑定要读取的数据源
2)使用FileInputStream对象中的方法read()读取文件
3)释放资源
循环一个个字节读取的代码示例:
public class FISRead {
public static void main(String[] args) throws IOException{
// 使用文件名称创建流对象
FileInputStream fis = new FileInputStream("read.txt");
// 定义变量,保存数据
int len ;
// 循环读取
while ((len = fis.read())!=-1) {
System.out.println((char)b);
}
// 关闭资源
fis.close();
}
}
使用字节数组 int read(byte[] b) 循环从输入流中读取一定数量的字节的代码演示:
public class FISRead {
public static void main(String[] args) throws IOException{
// 使用文件名称创建流对象.
FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
// 定义变量,作为有效个数
int len ;
// 定义字节数组,作为装字节数据的容器
byte[] b = new byte[2];
// 循环读取
while (( len= fis.read(b))!=-1) {
// 每次读取后,把数组的有效字节部分,变成字符串打印
System.out.println(new String(b,0,len));// len 每次读取的有效字节个数
}
// 关闭资源
fis.close();
}
}
回顾复习String类构造方法中的两个:
- String(byte[] bytes):把字节数组转换为字符串
- String(byte[] bytes,int offset, int len):把字节数组的一部分转换为字符串
offset:开始索引;length:长度
注意:int read(byte[] b)
1)byte[] b的作用:起到缓冲作用,存储每次读取到的多个字节
数组长度一般定义为1024(1KB)或1024的整数倍
2)方法的返回值int是什么:每次读取到的有效字节个数
40. java.io.Reader和子类java.io.FileReader
java.io.Reader:字符输入流,是所有字符输入流的最顶层的父类
java.io.Reader抽象类里,定义了一些共性的成员方法:
1)int read():读取单个字符返回
2)int read(char[] cbuf):一次性读取多个字符,将字符存入数组
3)void close():关闭该流并释放资源
java.io.FileReader extends InputStreamReader extends Reader:文件字符输入流
构造方法:
- FileReader(String name)
- FileReader(File file)
使用步骤类似于文件字节输入流
回顾复习String类构造方法中的两个:
- String(char[] value):把字符数组转换为字符串
- String(char[] bytes,int offset, int len):把字符数组的一部分转换为字符串
offset:开始索引;length:长度
循环一个个字符读取的代码示例:
public class FRRead {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileReader fr = new FileReader("read.txt");
// 定义变量,保存数据
int b ;
// 循环读取
while ((b = fr.read())!=-1) {
System.out.println((char)b);
}
// 关闭资源
fr.close();
}
}
使用字符数组 int read(char[] cbuf) 循环从输入流中读取一定数量的字符的代码演示:
public class FISRead {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileReader fr = new FileReader("read.txt");
// 定义变量,保存有效字符个数
int len ;
// 定义字符数组,作为装字符数据的容器
char[] cbuf = new char[2];
// 循环读取
while ((len = fr.read(cbuf))!=-1) {
System.out.println(new String(cbuf,0,len));
}
// 关闭资源
fr.close();
}
}
41. java.io.Writer和子类java.io.FileWriter
java.io.Writer:字符输出流,是所有字符输出流的最顶层的父类
java.io.Writer抽象类定义了一些子类共性的成员方法:
1)void write(int c):写入单个字符。
2)void write(char[] cbuf):写入字符数组。
3)abstract void write(char[] cbuf, int off, int len):写入字符数组的某一部分
4)void write(String str):写入字符串。
5)void write(String str, int off, int len):写入字符串的某一部分
6)void flush():刷新该流的缓冲
7)void close():关闭此流,但要先刷新它
java.io.FileWriter extends OutputStreamWriter extends Writer:文件字符输出流
构造方法:
- FileWriter(File file)
- FileWriter(String name)
使用步骤类似于文件字节输出流,就是多了一个刷新缓冲区的步骤
.flush() 和 .close() 方法的区别:
- .flush():刷新缓冲区,流对象还可以继续使用
- .close():先刷新缓冲区,然后通知系统释放资源,流对象不可在被使用了
续写和输出换行:类似于上面的文件输出字节流那样
使用2个参数的构造方法:
- FileWriter(String name, boolean append)
- FileWriter(File file, boolean append)
42. IO异常的处理
1)JDK1.7之前,使用try…catch…finally 来处理IO流中的异常
2)JDK1.7的新特性:
在try的后面可以添加一个 (),在括号里可以定义流对象,那么这个流对象的作用域就在try’中有效;try中的代码执行完毕后,会自动把流对象释放,不用写finally
格式:
try (创建流对象语句,如果多个,使用';'隔开) {
// 读写数据
} catch (IOException e) {
e.printStackTrace();
}
//代码演示:
public class HandleException2 {
public static void main(String[] args) {
// 创建流对象
try ( FileWriter fw = new FileWriter("fw.txt"); ) {
// 写出数据
fw.write("黑马程序员"); //黑马程序员
} catch (IOException e) {
e.printStackTrace();
}
}
}
3)JDK9的新特性:
try的前面可以先定义流对象;在try后面的()中直接引入流对象的名称;在try代码块执行完毕之后,流对象会被自动释放掉,也不用写finally
代码演示:
public class TryDemo {
public static void main(String[] args) throws IOException {
// 创建流对象
final FileReader fr = new FileReader("in.txt");
FileWriter fw = new FileWriter("out.txt");
// 引入到try中
try (fr; fw) {
// 定义变量
int b;
// 读取数据
while ((b = fr.read())!=-1) {
// 写出数据
fw.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
43. java.util.Properties集合 extends Hashtable< k,v >
- Properties类表示一个持久的属性集
- Properties可保存在流中或从流中加载
- Properties集合是唯一和IO流相结合的集合
可以使用Properties集合中的store()方法,把集合中的临时数据持久化写入硬盘
可以使用Properties集合中的load()方法,把硬盘中保存的文件(键值对),读取到集合中使用
- 属性列表中每个键及其对应的值都是一个字符串,Properties集合是一个双列集合
Properties集合中一些操作字符串特有的方法:
1)public Object setProperty(String key, String value):保存一对属性;相当于Hashtable的put方法
2)public String getProperty(String key):通过key找value,相当于Map集合中的get(key)方法
3)public Set< String > stringPropertyNames():返回此属性列表中的键值,相当于Map集合中的keySet()方法
store() 方法:
把集合中的临时数据,持久化写入到硬盘中存储
- void store(OutputStream out, String comments)
- void store(Writer writer, String comments)
参数:
- OutputStream out:字节输出流,不能写入中文
- Writer writer:字符输出流,可以写入中文
- String comments:注释,不能写入中文,一般使用空字符串
使用步骤:
- 创建Properties集合对象,添加数据
- 创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
- 使用Properties集合中的store()方法,把集合中的临时数据,写入硬盘
- 释放资源
load() 方法:
把硬盘中保存的文件(键值对)读取到集合中使用
- void load(InputStream inStream)
- void load(Reader reader)
参数:
- InputStream inStream:字节输入流,不能读取含有中文的键值对
- Reader reader:字符输入流,能读含有中文的键值对
注意:
1)存储键值对properties文件中,键与值默认的连接符号可以是 = 或 空格 等
2) 存储键值对properties文件中,可以使用#号进行注释,被注释的键值对不会被读取
3)存储键值对properties文件中,键与值默认是字符串,不能再加 引号
44. java.io.BufferedOutputStream字节缓冲输出流 extends OutputStream
构造方法:
- BufferedOutputStream(OutputStream out)
- BufferedOutputStream(OutputStream out, int size)
参数:
- OutputStream out:字节输出流,可以传递入FileOutputStream对象
- int size:指定缓冲流内部缓冲区大小,不指定则为默认
使用步骤:
1)创建FileOutputStream对象,构造方法中传入要输出的目的地
2)创建BufferedOutputStream对象,构造方法中传递入FileOutputStream对象,提高FileOutputStream对象的效率
3)使用BufferedOutputStream对象中的write()方法,把数据写入到内部的缓冲区
4)使用BufferedOutputStream对象中的flush()方法,把内部缓冲区中的数据刷新到文件中
5)close()方法释放资源(如果不再使用这个流对象,可以省略第4步,close会先刷新缓冲区,在关闭资源)
45. java.io.BufferedInputStream字节缓冲输入流 extends InputStream
构造方法:
- BufferedInputStream(InputStream in)
- BufferedInputStream(InputStream in, int size)
参数:
- InputStream in:字节输入流,可以传递入FileInputStream对象
- int size:指定缓冲流内部缓冲区大小,不指定则为默认
使用步骤:
1)创建FileInputStream对象,构造方法中传入要读取的数据源
2)创建BufferedInputStream对象,构造方法中传递入FileInputStream对象,提高FileInputStream对象的读取效率
3)使用BufferedInputStream对象中的read()方法,读取文件
4)释放资源
46. 读取效率最高:缓冲流+缓冲数组 代码示例
public class BufferedDemo {
public static void main(String[] args) throws FileNotFoundException {
// 记录开始时间
long start = System.currentTimeMillis();
// 创建流对象
try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));
){
// 读写数据
int len;
byte[] bytes = new byte[8*1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0 , len);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("缓冲流使用数组复制时间:"+(end - start)+" 毫秒");
}
}
47. java.io.BufferedWriter字符缓冲输出流 extends Writer
构造方法:
- BufferedWriter(Writer out)
- BufferedWriter(Writer out, int size)
参数:
- Writer out:字符输出流,可以传递入FileWriter对象
- int size:指定缓冲区大小,不写则默认大小
字符缓冲输出流的特有的成员方法:
- void newLine():写入一个行分隔符(换行符号);会根据不同的操作系统获取不同的行分隔符
使用步骤:
1)创建字符缓冲输出流对象,构造方法中传递入字符输出流
2)调用字符缓冲输出流中的write()方法,把数据写入到内存缓冲区中
3)调用字符缓冲输出流中的flush()方法,把内存缓冲区中的数据,刷新到文件中
4)释放资源
48. java.io.BufferedReader字符缓冲输入流 extends Reader
构造方法:
- BufferedReader(Reader in)
- BufferedReader(Reader in, int size)
字符缓冲输入流特有的成员方法:
- String readLine():读取一个文本行;读取一行数据
返回值:包含该行内容的字符串,不包含任何行终止符,如果已经到达流末尾,则返回null
使用步骤:类似于字符缓冲输出流
49. 字符缓冲输入流和输出流 代码示例
public class BufferedTest {
public static void main(String[] args) throws IOException {
// 创建map集合,保存文本数据,键为序号,值为文字
HashMap<String, String> lineMap = new HashMap<>();
// 创建流对象
BufferedReader br = new BufferedReader(new FileReader("in.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
// 读取数据
String line = null;
while ((line = br.readLine())!=null) {
// 解析文本
String[] split = line.split("\\.");
// 保存到集合
lineMap.put(split[0],split[1]);
}
// 释放资源
br.close();
// 遍历map集合
for (int i = 1; i <= lineMap.size(); i++) {
String key = String.valueOf(i);
// 获取map中文本
String value = lineMap.get(key);
// 写出拼接文本
bw.write(key+"."+value);
// 写出换行
bw.newLine();
}
// 释放资源
bw.close();
}
}
50. FileReader读取不同编码方式的文本时遇到的问题
java.io.FileReader可以读取IDEA默认的编码格式(UTF-8)的文件
但是,java.io.FileReader读取windows系统默认的中文编码格式GBK时,会产生乱码
- GBK:中文码表,使用2个字节存储一个中文
- UTF-8:国际标准码表,使用3个字节存储一个中文
FileReader的底层是FileInputStream读取字节,然后FileReader解码
FileWriter的底层是先编码为字节,然后用FIleOutputStream输出字节
51. 转换流java.io.OutputStreamWriter extends Writer
是字符流转换为字节流的桥梁;可以使用指定的编码表,将要写入字节流中的字符编码为字节
构造方法:
- OutputStreamWriter(OutputStream out):创建使用默认字符集的字符流。
- OutputStreamWriter(OutputStream out, String charsetName):创建使用指定字符集的字符流
参数:
- OutputStream out:字节输出流
- String charsetName:指定编码表名称
不区分大小写,可以是 utf-8/UTF-8、gbk/GBK、…
不指定,默认使用UTF-8
使用步骤:
1)创建OutputStreamWriter对象,构造方法传递字节输出流和指定的编码表名称
2)使用OutputStreamWriter对象中的write()方法,把字符转换为字节存储到缓冲区中
3)使用OutputStreamWriter对象中的flush()方法,把内存缓冲区的字节刷新到文件中
4)释放资源
52. 转换流java.io.InputStreamReader extends Reader
是字节流转换为字符流的桥梁;它使用指定的编码表读取字节并将其解码为字符
构造方法:
- InputStreamReader(InputStream in):创建一个使用默认字符集的字符流
- InputStreamReader(InputStream in, String charsetName):创建一个指定字符集的字符流
注意:
构造方法中指定的编码表名称要和读取的原文件的相同,否则会乱码
使用步骤:
类似上面的那个转换流
53. 转换流 代码示例
public class TransDemo {
public static void main(String[] args) {
// 1.定义文件路径
String srcFile = "file_gbk.txt";
String destFile = "file_utf8.txt";
// 2.创建流对象
// 2.1 转换输入流,指定GBK编码
InputStreamReader isr = new InputStreamReader(new FileInputStream(srcFile) , "GBK");
// 2.2 转换输出流,默认utf8编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(destFile));
// 3.读写数据
// 3.1 定义数组
char[] cbuf = new char[1024];
// 3.2 定义长度
int len;
// 3.3 循环读取
while ((len = isr.read(cbuf))!=-1) {
// 循环写出
osw.write(cbuf,0,len);
}
// 4.释放资源
osw.close();
isr.close();
}
}
54. java.io.ObjectOutputStream对象的序列化流 extends OutputStream
把对象以流的方式写入到文件中保存
构造方法:
- public ObjectOutputStream(OutputStream out)
参数:字节输出流
特有的成员方法:
- void writeObject(Object obj):将指定的对象写入ObjectOutputStream
使用步骤:
1)创建ObjectOutputStream对象,构造方法中传递入字节输出流
2)使用ObjectOutputStream对象中的writeObject方法,把对象写入到文件中
3)释放资源
代码示例:
public class SerializeDemo{
public static void main(String [] args) {
Employee e = new Employee();
e.name = "zhangsan";
e.address = "beiqinglu";
e.age = 20;
try {
// 创建序列化流对象
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));
// 写出对象
out.writeObject(e);
// 释放资源
out.close();
fileOut.close();
System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
} catch(IOException i) {
i.printStackTrace();
}
}
}
注意:
- 该类必须实现java.io.Serializable接口来启用序列化或反序列化功能
- Serializable是一个标记型接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException没有序列化异常
- 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient关键字修饰。
55. java.io.ObjectInputStream对象的反序列化流 extends InputStream
把文件中保存的对象,以流的方式读取到出来用
构造方法:
- public ObjectInputStream(InputStream in)
参数:字节输入流
特有的成员方法:
- Object readObject():从ObjectInputStream读取对象
使用步骤:
类似于对象的序列化流
示例代码:
public class DeserializeDemo {
public static void main(String [] args) {
Employee e = null;
try {
// 创建反序列化流
FileInputStream fileIn = new FileInputStream("employee.txt");
ObjectInputStream in = new ObjectInputStream(fileIn);
// 读取一个对象
e = (Employee) in.readObject();
// 释放资源
in.close();
fileIn.close();
}catch(IOException i) {
// 捕获其他异常
i.printStackTrace();
return;
}catch(ClassNotFoundException c) {
// 捕获类找不到异常
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
// 无异常,直接打印输出
System.out.println("Name: " + e.name); // zhangsan
System.out.println("Address: " + e.address); // beiqinglu
System.out.println("age: " + e.age); // 0
}
}
能够反序列化的前提:
- 类必须实现了Seriallizable接口
- 必须存在类对应的class文件;当不存在对象的那个类的class文件时,readObject()方法会抛出ClassNotFoundException异常(class文件找不到异常)
InvalidClassException 异常:
当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操
作也会失败,抛出一个InvalidClassException 异常。
InvalidClassException 异常原理:
- 编译器(javac.exe)会把.java文件编译成.class文件时,类实现了Seriallizable接口,就会根据类的定义,给.class文件添加一个序列号"serialVersionUID"
- 反序列化的时候,会使用.class文件中的序列号和保存得到的.txt文件中的序列号进行比较;
如果一样,反序列化成功;如果不一样,抛出序列化冲突异常:InvalidClassException
手动给类添加一个序列号"serialVersionUID",格式在Seriallizable接口规定了:
声明名为"serialVersionUID"字段,必须是静态(static),最终的(final)的long型字段
例如:
public class Employee implements java.io.Serializable {
// 加入序列版本号
private static final long serialVersionUID = 1L;
public String name;
public String address;
// 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
public int eid;
public void addressCheck() {
System.out.println("Address check : " + name + " -- " + address);
}
}
56. 序列化集合
若要在文件中保存多个对象,可以把多个对象保存到一个集合中,然后对集合进行序列化和反序列化的操作
57. transient关键字 和static关键字 修饰的成员变量不能被序列化
- transient关键字:瞬态关键字
被瞬态关键字修饰的成员变量,不能被序列化
- static关键字:静态关键字
静态优先于非静态加载到内存中(静态资源优先于对象进入到内存中),所以被static修饰的成员变量不能被序列化
58. 打印流java.io.PrintStream extends FilterOutputStream extends OutputStream
为其他输出流添加了功能,使他们能够方便地打印各种数据值表示形式
特点:
- 只负责数据的输出,不负责数据的读取
- 与其他输出流不同,PrintStream永远不会抛出IOException异常
特有的成员方法:
- void print(任意数据类型的值)
- void println(任意数据类型的值并换行)
构造方法:
- PrintStream(File file):目的地是文件
- PrintStream(OutputStream out):目的地是字节输出流
- PrintStream(String filename):目的地是文件路径
注意:
- 如果使用继承父类来的write()方法写数据,那么查看数据的时候会查询编码表,例如:97 -> a
- 如果使用自己特有的方法print/println写数据,会原样输出,例如:97 -> 97
System.setOut(PrintStream out)方法:
输出语句默认在控制台输出,使用System.setOut()方法来改变输出语句的目的地(打印流的流向)
代码示例:
public class PrintDemo {
public static void main(String[] args) throws IOException {
// 调用系统的打印流,控制台直接输出A
System.out.println("A");
// 创建打印流,指定文件的名称
PrintStream ps = new PrintStream("ps.txt");
// 设置系统的打印流流向,输出到ps.txt
System.setOut(ps);
// 调用系统的打印流,ps.txt中输出B
System.out.println("B");
ps.close();
}
}