从上次自习算起至现在已经过去四天,这四天我们分别学习了多线程的等待唤醒机制、线程池,Lambda——Day07,IO流的File类、递归遍历多级目录思想——Day08、InputStream、OutputSteam普通流——Day09,还有缓冲流、转换流、对象流、打印流——Day10。知识点总结如下:
Day07
一、线程间通信
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
例如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。
1、等待唤醒机制
简单来说就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。wait/notify 就是线程间的一种(等待唤醒)协作机制。详细分析如下图:
2、等待唤醒中的方法
java.lang.Object类:
// 成员方法
void wait(): 使用锁对象调用, 当前线程进入WAITING无限等待状态, 直到被其他线程唤醒
void notify(): 使用锁对象调用, 随机唤醒一个处于等待状态的线程
void notifyAll(): 使用锁对象调用, 唤醒所有处于等待状态的线程
二、线程池
1、线程池思想概述
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。所以这时候我们就需要一个线程池。线程池就是一个容纳多个线程的容器。线程池其中的线程可以反复使用, 省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源。大幅提升了效率。
工作原理:提前创建好多个线程对象, 放在集合中. 多个任务来了反复使用这些线程对象来执行
详细分析:
2、线程池的使用
java.util.concurrent.Executors类: 线程池工厂类, 用于管理线程池
// 静态方法:
static ExecutorService newFixedThreadPool(int nThreads): 创建固定数量线程的线程池(常用)
java.util.concurrent.ExecutorService接口: 真正执行任务的线程池服务
// 成员方法:
Future submit(Runnable task): 提交一个Runnable任务
void shutdown(): 通知线程执行完任务后关闭. 如不调此方法, 则线程执行完任务后仍在运行以便重复使用
线程池的创建和使用步骤:
1. 使用Executors的静态方法 newFixedThreadPool(int nThreads) 创建线程池ExecutorService
2. 创建一个任务类, 实现Runnable接口, 重写run()方法
3. 调用ExecutorService对象的 submit(Runnable task) 方法, 传递任务给线程池, 执行任务
4. 调用ExecutorService对象的 shutdown() 方法, 销毁线程池 (不建议执行)
三、函数式编程思想:Lambda表达式
1、函数式编程思想概述:强调做事 (不关心用什么对象, 重写什么方法)。
2、Lambda表达式
Lambda使用前提:
1) Lambda只能用于接口, 且"接口中有且仅有一个抽象方法"(也称为"函数式接口")普通类, 抽象类不能用 2) 使用Lambda必须具有上下文推断方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例(简而言之: 作为参数类型的接口, 必须是函数式接口)
Lambda表达式的3个部分: 1) 一些参数 () 接口中抽象方法的参数列表. 没参数就空着; 有参数就写, 多个参数用逗号分隔 2) 一个箭头 -> 将参数传递给方法体 3) 一段代码 {} 重写接口抽象方法的方法体 格式: // 写成一行 (参数列表) -> {一些重写方法的代码} // 写成多行 (参数列表) -> { 一些重写方法的代码 }
3、Lambda省略格式
省略原则: 可推导的都可省略 (凡是能根据前后代码能猜测出来的代码, 都可以省略不写) 可以省略的部分: 1. (参数列表): 参数"类型"可以省略 2. (参数列表): 如果参数只有1个, 则"类型"和"小括号"都可以省略 ,如:a -> sout(a) 3. {一些代码}: 如果只有一条代码, 则"大括号", "return", "分号"都可以"一起省略"。
Day08
一、File类
java.io.File类: 文件和目录的路径名的抽象表现形式, 主要用于文件和目录的创建, 查找和删除等操作 我们可以对用File类进行的操作: 创建文件/目录 删除文件/目录 获取文件/目录 判断文件/目录是否存在 对目录进行遍历 获取文件的大小 重要英文单词的含义: (起变量名时会用到) file: 文件 directory: 目录 path: 路径
1、File类的静态成员变量
// 静态成员变量
static String pathSeparator: 路径分隔符的字符串形式
static char pathSeparatorChar: 路径分隔符的char形式
Windows系统是 分号;
Linux系统是 冒号:
static String separator: 文件名称分隔符的字符串形式
static char separatorChar: 文件名称分隔符的char形式
Window系统是 反斜杠\
Linux系统是 正斜杠/
绝对路径和相对路径 2、绝对路径:以盘符开始的路径 如: “D:\\a\hi.txt” 相对路径:不以盘符开始的简化路径,相当于项目的根目录 如:”a\\1.mp3”, “123.txt” 注意事项: 1) 路径不区分大小写在(windows系统中部分大小写,Linux,Mac区分) 2)路径一般写成字符串,而字符串中一个\是转义,所以要写两个\\
3、File类方法
java.io.File类: 文件和目录的路径名的抽象表现形式, 主要用于文件和目录的创建, 查找和删除等操作
// 构造方法(创建了File对象, 并将其指向该路径. 不会真正在磁盘上创建这个文件)
File File(String pathname): 根据 路径字符串 封装一个File对象
File File(String parent, String child): 根据 父路径字符串 和 子路径字符串 封装File对象
File File(File parent, String child): 根据 父路径的File对象 和 子路径 封装File对象
java.io.File类
// 常用获取方法
String getAbsolutePath(): 返回此File的绝对路径名字符串
String getPath(): 获取File对象的封装路径 (创建对象时传入的路径)
String getName(): 获取File对象的文件名或目录名
long length(): 获取File表示的文件大小的字节数 (不能获取目录的大小)
获取目录文件夹的大小做不到 会得到结果0/4096 都是错的 4096是1024*4也是不对的
getAbsolutePath()、getPath()、getName()、这三个获取方法和有没有这个文件没有关系 他不会是核实你的文件是不是真实存在的
java.io.File类 // 常用判断方法 boolean exists(): 判断File对象代表的文件或目录是否实际存在 boolean isDirectory(): 判断File表示的是否为目录 boolean isFile(): 判断File表示的是否为文件
全都是boolean类型的返回值。电脑的硬盘中只有文件/文件夹,两个方法是互斥的。这两个方法使用前提,路径必须是存在的,否则返回false
// 常用创建删除方法
boolean createNewFile(): 当文件不存在时, 创建一个新的空文件
boolean delete(): 删除由此File表示的文件或目录. (删除目录时必须是空目录)
boolean mkdir(): 创建File表示的目录
boolean mkdirs(): 创建File表示的多级目录
delete方法是直接在硬盘上删除文件/文件夹,不走回收站 一定要慎重
// 常用获取目录中内容的方法 String[] list(): 获取当前File目录下的所有子文件或目录的名字数组 File[] listFiles(): 获取当前File目录中的所有子文件或目录的File对象数组
只能用表示目录的File对象调用用文件的File对象, 或者路径不存在, 调用会报错
二、递归
递归思想:遇到一个问题时, 将该问题拆解成可以解决的小问题, 如果解决不了, 继续拆解为更小的问题. 如果小问题解决了, 大问题也就能够解决 Java中实现递归的方式:方法内部调用方法自己 (所以必须定义方法)。
递归的分类:
直接递归: 方法自己调用方法。
间接递归: A方法调用B方法, B方法调用C方法, C方法调用A方法
递归时的注意事项:
1. 递归要有限定条件(出口), 保证递归能够停止(就是在某种情况下方法不再调用自己), 否则会栈内存溢出
2. 递归次数不能太多, 否则会栈内存溢出
3. 构造方法不能递归
递归的使用前提:调用方法时, 方法的主体不变, 但每次传递的参数值不同, 可以使用递归 。
构造方法禁止递归:构造方法是创建对象使用的,一直递归会导致内存中有无数多个对象,直接编译报错递归求和的思路 用递归完成N以内求和 把解决不了的问题定义成方法(求5-1的和) 如果小问题能解决 直接写代码实现。
三、FileFilter文件过滤器的原理和使用java.io.File类: Filter过滤器File[] listFiles(FileFilter filter): 通过File对象过滤, 返回文件过滤器过滤后的File对象数组File[] listFiles(FilenameFilter filter): 通过File对象的文件名过滤, 返回文件过滤器过滤后的File对象数组java.io.FileFilter接口: 用于File对象的过滤器boolean accept(File pathName): true则会将参数的File对象加入返回的File[], false则不加入java.io.FilenameFilter接口:boolean accept(File dir, String name): true则会将参数的File对象加入返回的File[], false则不加入
Day09
一、IO流
i就是input 输入(读取) o就是output 输出(写入) 流:数据(字符,字节) 1个字符 = 2个字节 1个字节 = 8个二进制位 输入:从硬盘读取到内存 输出:从内存写入到硬盘 (入和出都是相对于内存来说的)
1、分类
①字节流 (读写字节: byte, 可以读写所有类型的文件, 包括视频, 图片, 音频, 文本等) 字节输入流:InputStream FileInputStream 字节输出流:OutputStream FileOutputStream
②字符流 (读写字符: char, String, 只能读写文本文件) 字符输入流:Reader FileReader 字符输出流:Writer FileWriter
2.字节输出流:
// 成员方法
void close() :释放资源
void flush() :刷新缓冲区(对于字节流来说没有作用)
// 写字节的成员方法
abstract void write(int b): 一次写一个字节 (参数是int是为了代码中写表示byte的整数方便不用强转)
void write(byte[] b): 一次写一个字节数组
void write(byte[] b, int off, int len): 一次写一个字节数组的一部分
java.io.FileOutputStream类: 文件字节输出流 (向文件写数据)
// 构造方法
FileOutputStream(String name): 通过文件路径创建文件字节输出流
FileOutputStream(File file): 通过File对象创建文件字节输出流
FileOutputStream(String name, boolean append): 通过文件路径创建文件字节输出流, true可以续写
FileOutputStream(File file, boolean append): 通过File对象创建文件字节输出流, true可以续写
换行符: windows: \r\n linux: /n mac: /r
3.字节输入流: java.io.InputStream抽象类: 字节输入流 (顶层类) // 常用成员方法 void close(): 释放资源 // 读数据的方法 int read(): 一次读一个字节. 读到文件末尾返回-1 (返回int也是为了代码编写方便)
循环读取标准格式:
[Java] 纯文本查看 复制代码 FileInputStream fis = new FileInputStream("模块名\\文件名");
// 一次读一个字节:
int by; // int变量用来存储每次读到的数据
while ((by = fis.read()) != -1) {
System.out.print((char)by); // 不要换行, 文件中自带换行的byte
}
fis.close();
/*int read(byte[] b): 一次读一个字节数组. 读到的字节存放在参数中的字节数组中, 返回int值是本次读到的字节的个数. 读到文件末尾返回-1*/
FileInputStream fis = new FileInputStream("模块名\\文件名");
byte[] bytes = new byte[1024]; // 定义字节数组, 用于保存每次读取到的数据
int len; // 定义int变量, 用于保存每次读取到的长度
while ((len = fis.read(bytes)) != -1) {
String s = new String(bytes, 0, len); // 将读取到的字节转换为字符串, 读到多少个就转换多少个
System.out.print(s);
}
fis.close(); // 释放资源
fis.close();
3.字符流输出:
java.io.Reader抽象类: 字符输入流 (顶层)
// 常用成员方法
void close() :关闭此流并释放与此流相关联的任何系统资源
// 读数据的方法
int read(): 一次读一个字符char, 返回读到的字符. 读到文件末尾返回-1 (返回int为了代码编写方便)
int read(char[] cbuf): 一次读取一个字符数组char[]. 返回读取的字符个数. 读到文件末尾返回-1
java.io.FileReader类: 文件字符输入流
// 构造方法
FileReader(File file): 根据File对象创建文件字符输入流
FileReader(String fileName): 根据File对象创建文件字符输入流
java.io.Writer抽象类: 字符输出流 (顶层类)
// 常用成员方法
abstract void close(): 刷新缓冲区并释放资源
abstract void flush() :刷新缓冲区
// 写
void write(int c): 写一个字符 (int类型为了代码编写方便)
void write(char[] cbuf): 写一个字符数组
abstract void write(char[] b, int off, int len): 写一个字符数组的一部分
void write(String str): 写一个字符串
void write(String str, int off, int len): 写一个字符串的一部分
java.io.FileWriter类: 文件字符输出流
// 构造方法
FileWriter(File file): 通过File对象创建文件字符输出流
FileWriter(String fileName): 通过文件路径创建文件字符输出流
FileWriter(File file, boolean append): 通过File对象创建文件字符输出流. 第二个参数为true可以续写
FileWriter(String fileName, boolean append): 通过文件路径创建文件字符输出流. 第二个参数为true可以续写
4、IO异常处理
IO流的标准格式:
[AppleScript] 纯文本查看 复制代码 [/font]
[font=微软雅黑]FileWriter fw = null;
[/font][font=微软雅黑] try {[/font]
[font=微软雅黑] //IO流对象的创建, 操作等代码[/font]
[font=微软雅黑] fw = new FileWriter("d:\\09_IOAndProperties\\g.txt", true);[/font]
[font=微软雅黑] for (int i = 0; i <10 ; i++) {[/font]
[font=微软雅黑] fw.write("HelloWorld"+i+"\r\n");[/font]
[font=微软雅黑] }[/font]
[font=微软雅黑] } catch (IOException e) {[/font]
[font=微软雅黑] e.printStackTrace();[/font]
[font=微软雅黑] } finally {[/font]
[font=微软雅黑] // 释放资源[/font]
[font=微软雅黑] if(fw != null){[/font]
[font=微软雅黑] try {[/font]
[font=微软雅黑] fw.close();[/font]
[font=微软雅黑] } catch (IOException e) {[/font]
[font=微软雅黑] e.printStackTrace();[/font]
[font=微软雅黑] }[/font]
[font=微软雅黑] }[/font]
[font=微软雅黑] }
Day10
一、缓冲流
字节缓冲流: BufferedInputStream , BufferedOutputStream 字符缓冲流: BufferedReader , BufferedWriter 缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。 1、字节缓冲流:构造方法:l public BufferedInputStream(InputStream in):创建一个 新的缓冲输入流。l public BufferedOutputStream(OutputStream out): 创建一个新的缓冲输出流。构造举例,代码如下// 创建字节缓冲输入流 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt")); // 创建字节缓冲输出流 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt")); 使用步骤: 1.创建FileInputStream对象, 构造方法中绑定要读取的数据源 2.创建BufferedInputStream对象, 构造方法中传递FileInputStream对象, 提高FileInputStream效率 3.使用BufferedInputStream对象中的方法 read(), 读取文件 4.释放资源 close() 格式如下: [Java] 纯文本查看 复制代码 [/font][/align][align=left][font=微软雅黑]FileInputStream fis = new FileInputStream("文件路径");[/font][/align][align=left][font=微软雅黑] BufferedInputStream bis = new BufferedInputStream(fis);[/font][/align][align=left][font=微软雅黑] byte[] bytes = new byte[1024];[/font][/align][align=left][font=微软雅黑] int len;[/font][/align][align=left][font=微软雅黑] while ((len = bis.read(bytes)) != -1) {[/font][/align][align=left][font=微软雅黑] System.out.println(new String(bytes, 0, len));[/font][/align][align=left][font=微软雅黑] }[/font][/align][align=left][font=微软雅黑] bis.close();[/font][/align][align=left][font=微软雅黑] 2、字符缓冲流 构造方法:public BufferedReader(Reader in) :创建一个 新的缓冲输入流。public BufferedWriter(Writer out) : 创建一个新的缓冲输出流。特有方法:BufferedReader: public String readLine() : 读一行文字。BufferedWriter: public void newLine() : 写一行行分隔符,由系统属性定义符号。二、转换流 字符编码和字符集: 字符:计算机中储存的信息都是用二进制数表示,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符储存到计算机中,称为编码。反之,将储存在计算机中的二进制按照某种规则解析显示出来,称为解码。 字符编码:就是一套自然语言的字符与二进制数之间的对应规则; 字符集:是一个系统支持的所有字符的合集,包括各国家文字、标点符号、图形符号、数字等。 在IDEA中,使用FileReader读取项目中的文本文件。由于IDEA的设置,都是默认的 UTF-8 编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。 java.io.OutputStreamWriter类: 输出转换流. 字符流通往字节流的桥梁。 // 构造方法 OutputStreamWriter(OutputStream out): 使用默认编码表创建转换流 OutputStreamWriter(OutputStream out, String charsetName): 使用指定编码表创建转换流 [Java] 纯文本查看 复制代码 [/font][/align][align=left][font=微软雅黑]// 使用默认UTF-8
OutputStreamWriter o = new OutputStreamWriter(new FileOutputStream("a.txt"));[/font][/align][align=left][font=微软雅黑] [Java] 纯文本查看 复制代码 [/font][/align][align=left][font=微软雅黑]// 使用指定的GBK
OutputStreamWriter o = new OutputStreamWriter(new FileOutputStream("a.txt"), "GBK");[/font][/align][align=left][font=微软雅黑] java.io.InputStreamReader类: 输入转换流. 字节流通往字符流的桥梁。 // 构造方法 InputStreamReader(InputStream in): 使用默认编码表创建转换流 InputStreamReader(InputStream in, String charsetName): 使用指定编码表创建转换流 [Java] 纯文本查看 复制代码 [/font][/align][align=left][font=微软雅黑]// 使用默认UTF-8
InputStreamReader r = new InputStreamReader(new FileInputStream("a.txt"));[/font][/align][align=left][font=微软雅黑] [Java] 纯文本查看 复制代码 [/font][/align][align=left][font=微软雅黑]// 使用指定的GBK
InputStreamReader r = new InputStreamReader(new FileInputStream("a.txt"), "GBK");[/font][/align][align=left][font=微软雅黑] 三、序列化 Java提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该 对象的数据 、 对象的类型 和 对象中存储的数据 等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据 、 对象的类型 和 对象中存储的数据 信息,都可以用来在内存中创建对象。 OutputStream |_ ObjectOutputStream类: 对象字节输出流 InputStream |_ ObjectInputStream类: 对象字节输入流 1、ObjectOutputStream类、序列化 java.io.ObjectOutputStream类: 对象字节输出流 // 构造方法 ObjectOutputStream(OutputStream out) // 特有成员方法 void writeObject(Object obj): 将对象写,输出 演示: [Java] 纯文本查看 复制代码 [/align][align=left]// 创建对象输出流[/align] ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.txt"));
[align=left][font=微软雅黑][size=3]// 写对象[/size][/font][/align][align=left][font=微软雅黑][size=3] Student s = new Student("小美女", 18);[/size][/font][/align][align=left][font=微软雅黑][size=3] oos.writeObject(s);[/size][/font][/align][align=left][font=微软雅黑][size=3] // 释放资源[/size][/font][/align][align=left][font=微软雅黑][size=3] oos.close();[/size][/font][/align][font=微软雅黑][size=3]
[/size][/font]
注意: 被读写的对象的类, 必须实现 java.io.Serializable 接口, 否则会抛出 NotSerializableException 该类的所有属性也都必须是可序列化的. (实现 java.io.Serializable 接口) 如果某个属性不需要序列化, 则需要用 transient 修饰为瞬态的
如果要将集合序列化, 那么集合中存储的对象也必须是可序列化的 2、ObjectInputStream类、反序列化 java.io.ObjectInputStream类: 对象字节输入流 // 构造方法 ObjectInputStream(InputStream in) // 特有成员方法 Object readObject(): 读取对象 演示: [Java] 纯文本查看 复制代码 [/align][align=left]// 创建对象输入流[/align][align=left] ObjectInputStream oos = new ObjectInputStream(new FileInputStream("student.txt"));[/align][align=left] // 读对象[/align][align=left] Object o = oos.readObject();[/align][align=left] Student s = (Student)o;[/align][align=left] System.out.println(s);[/align][align=left] // 释放资源[/align][align=left] oos.close();[/align][align=left] 补充: 1、避免属性序列化 static 修饰的成员变量属于类不属于对象, 所以不能序列化 transient 修饰的成员变量, 不能被序列化
如果对象的某个属性不希望被序列化, 可以使用transient修饰, 这样就不会被对象流写到文件中 2、InvalidClassException异常: 原因和解决方案 serialVersionUID序列号的作用: 序列化操作时, 会根据类生成一个默认的 serialVersionUID 属性, 写入到文件中. 该版本号是根据类中成员计算出来的一个数值. 当类的成员发生改变时, 序列版本号也会发生变化 当代码中的类和读取的对象序列版本号不一致时, 就会抛出InvalidClassException,为了避免这种问题, 我们可以手动写死这个序列号, 这样无论成员如何变化, 序列版本号都是一样的
|