黑马程序员技术交流社区

标题: [石家庄校区]就业班SEday07-10笔记 [打印本页]

作者: 休人负我    时间: 2018-11-26 14:54
标题: [石家庄校区]就业班SEday07-10笔记
本帖最后由 休人负我 于 2018-12-10 11:26 编辑

day07

线程间通信

多个线程在处理同一个资源, 但是多个线程的处理动作却不相同(线程的任务不同, 需要协调合作)

为什么要进行线程间通信:
        通常是竞争关系: 多个线程并发执行时, 在默认情况下CPU是随机切换线程的.
        有时也"需要合作": 当我们需要多个线程来共同完成一件任务, 并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信, 以此来帮我们达到多线程共同操作一份数据

如何实现线程间通信
        "等待唤醒机制":
wait/notify, 就是"线程间的一种协作机制", 用于实现线程间通信

等待唤醒中的方法
ava.lang.Object类:
// 成员方法 (只能通过"锁对象"调用)
void notify(): 随机唤醒在同一个锁对象上的某一个处于等待状态的线程
void notifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程
void wait(): 让当前线程处于无限等待状态, 同时释放锁

wait和notify/notifyAll的执行原理:
        wait:
                线程不再活动, 不再参与调度, 进入 wait set 中, 因此不会浪费 CPU 资源, 也不会去竞争锁, 这时的线程状态即是"WAITING". 它还要等着别的线程执行"通知(notify)", 让在锁对象上等待的线程从 wait set 中释放出来, 重新进入到调度队列(ready queue)中

        notify/notifyAll:
                哪怕只通知了一个等待的线程, 被通知线程也不能立即恢复执行, 因为它当初中断的地方是在同步块内, 而
此刻它已经不持有锁, 所以它需要"再次尝试去获取锁"(很可能面临其它线程的竞争), 成功后才能在当初调用 wait() 之后的地方恢复执行

        总结如下:
                如果能获取锁, 线程就从"WAITING"状态变成"RUNNABLE"状态
                否则, 从 wait set 出来, 又进入entry set, 线程就从"WAITING"状态又变成"BLOCKED"状态


调用 wait() 和 notify() 需要注意的细节:
        1. wait() 与 notify() 必须要由"同一个锁对象"调用
                因为对应的锁对象可以通过 notify() 唤醒使用同一个锁对象调用的 wait() 后的线程
        2. wait() 与 notify() 是属于Object类的方法
                因为锁对象可以是任意对象, 而任意对象的所属类都是继承了Object类的
        3. wait() 与 notify() 必须要在"同步代码块"或者是"同步方法"中使用
                因为必须要通过锁对象调用这2个方法

线程池

普通创建线程方式的缺点:
        "创建"线程和"销毁"线程都是比较占用内存和CPU的操作.
        对于一些数量多, 执行时间短的任务, 频繁的创建和销毁线程来执行, 会降低程序运行效率.
        
线程池:
        一个容纳多个线程的容器(集合)
        
线程池可以解决的问题:
        其中的线程可以反复使用, 省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源
        
线程池的工作原理:
        提前创建好多个线程对象, 放在集合中. 多个任务来了反复使用这些线程对象来执行

线程池的代码实现
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表达式

面向对象: 强调"用哪个对象的哪个方法"来做事 (注重语法形式: 继承 方法重写)
函数式: 强调"传入的参数 和 要执行的代码"

函数式编程的好处:
                简化代码编写 (使用 λ Lambda表达式, 简化匿名内部类的代码)

Lambda标准格式
Lambda表达式的3个部分:
    1. 一些参数 ()
                接口中抽象方法的参数列表. 没参数就空着; 有参数就写, 多个参数用逗号分隔
    2. 一个箭头 ->
                将参数传递给方法体
    3. 一段代码 {}
                重写接口抽象方法的方法体

Lambda省略格式和使用前提

省略原则:
        "可推导的都可省略" (凡是能根据前后代码能猜测出来的代码, 都可以省略不写)
        
    可以省略的部分:
        1. (参数列表): 参数"类型"可以省略 (a, b) -> {}
        2. (参数列表): 如果参数只有1个, 则"类型"和"小括号"都可以省略  a -> sout(a)
        3. {一些代码}: 如果只有一条代码, 则"大括号", "return", "分号"都可以"一起省略"
        
            
*函数式接口:
        函数式接口: "有且仅有一个抽象方法的接口"
        但函数式接口对于 哪些方法算作抽象方法 有特殊规定:
                1. 有方法体的方法"不算作"抽象方法, 如默认方法, 静态方法, 私有方法
                2. 如果一个抽象方法 与 java.lang.Object类中的方法 定义相同的, 也"不算作"抽象方法
                因为任何实现本接口的实现类, 都会直接或间接继承java.lang.Object类的public的方法, 所以在创建实现类时其实不用重写该抽象方法, 也就不算作抽象方法


Lambda表达式的使用前提:
    1. Lambda只能用于接口, 且"接口中有且仅有一个抽象方法"(也称为"函数式接口")
        普通类, 抽象类不能用
    2. 使用Lambda必须具有上下文推断
        接口中只能有一个抽象方法, 才能推断出来重写的是这个抽象方法
    简而言之: 参数类型必须是函数式接口



day08


File类

java.io.File类: "文件"和"目录"的路径名的抽象表现形式, 主要用于文件和目录的创建, 查找和删除等操作
        private String path;

File类的静态成员变量

static String pathSeparator: 路径分隔符的字符串形式
static char pathSeparatorChar: 路径分隔符的char形式
            Windows系统是 分号;  
        Linux系统是 冒号:
static String separator: 文件名称分隔符的字符串形式
static char separatorChar: 文件名称分隔符的char形式
            Window系统是 反斜杠\
        Linux系统是 正斜杠/

绝对路径和相对路径

绝对路径:
        以盘符开始的路径

相对路径:
        不以盘符开始的简化路径. IDEA项目, 相对于项目的根目录

注意事项:
        1. 路径不区分大小写 (在Windows系统中不区分大小写, Linux, Mac区分)
        2. 路径一般写成字符串, 而字符串中一个\是转义, 所以要写两个\\

构造方法

File File(String pathname): 根据 路径字符串 封装一个File对象
File File(String parent, String child): 根据 父路径字符串 和 子路径字符串 封装File对象
File File(File parent, String child): 根据 父路径的File对象 和 子路径 封装File对象

获取方法

String getAbsolutePath(): 返回此File的绝对路径名字符串
String getPath(): 获取File对象的封装路径 (创建对象时传入的路径)
String getName(): 获取File对象的文件名或目录名  d:\a\b\c\aaa.txt
long length(): 获取File表示的"文件"大小的字节byte数 (不能获取目录的大小)

判断方法

boolean exists(): 判断File对象代表的文件或目录是否实际存在
boolean isDirectory(): 判断File表示的是否为目录
boolean isFile(): 判断File表示的是否为文件

创建删除方法

boolean createNewFile(): 当文件不存在时, 创建一个新的空文件
        false: 路径已经存在(无论文件还是目录)   
             抛IO异常: 写的路径不符合逻辑 (Y:\\a.txt\dsfsd)
boolean delete(): 删除由此File表示的文件或目录.
        删除目录时: 必须是空目录
boolean mkdir(): 创建File表示的目录  "d:\\a\\b\\c\\我的目录"
        false: 1. 路径已经存在(无论文件还是目录)   2. 写的路径不符合逻辑 (Y:\\a.txt\dsfsd)
boolean mkdirs(): 创建File表示的多级目录   "d:\\a\\b\\c\\我的目录"
        false: 1. 路径已经存在(无论文件还是目录)   2. 写的路径不符合逻辑 (Y:\\a.txt\ds)

遍历目录方法

String[] list(): 获取当前File目录下的所有子文件或目录的名字数组
File[] listFiles(): 获取当前File目录中的所有子文件或目录的File对象数组

递归

遇到一个问题时, 将该问题拆解成可以解决的小问题, 如果解决不了, 继续拆解为更小的问题. 如果小问题解决了, 大问题也就能够解决

Java中实现递归的方式
        方法内部调用方法自己 (所以必须定义方法)

递归的分类
        直接递归: 方法自己调用方法
        间接递归: A方法调用B方法, B方法调用C方法, C方法调用A方法
        
递归时的注意事项
    1. 递归要有限定条件(出口), 保证递归能够停止(就是在某种情况下方法不再调用自己), 否则会栈内存溢出
    2. 递归次数不能太多, 否则会栈内存溢出
    3. 构造方法不能递归

递归的使用前提
        调用方法时, 方法的主体不变, 但每次传递的参数值不同, 可以使用递归

FileFilter文件过滤器

java.io.File类:  Filter过滤器
        File[] listFiles(FileFilter filter): 返回文件过滤器过滤后的File对象数组
        File[] listFiles(FilenameFilter filter): 返回文件过滤器过滤后的File对象数组

java.io.FileFilter接口: 用于File对象的过滤器
        boolean accept(File pathName): true则会将参数的File对象加入返回的File[], false则不加入

java.io.FilenameFilter接口: 将File对象拆分为父路径和子路径来判断的过滤器
        boolean accept(File dir, String name): true则会将参数的File对象加入返回的File[], false则不加入
                dir: 被找到的文件所在的目录 (父路径)
                name: 文件的名称          (子路径)




day09


IO概述

把这种数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为输入input 和输出output ,即流向内存是输入流,流出内存的输出流。
Java中I/O操作主要是指使用java.io包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。

根据数据的流向分为:输入流和输出流。
格局数据的类型分为:字节流字符流

字节流

字节输出流【OutputStream】

java.io.OutputStream抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。



FileOutputStream类

java.io.FileOutputStream类是文件输出流,OutputStream的子类,用于将数据写出到文件。

构造方法

写出字节数据
write(int b) 方法,每次可以写出一个字节数据

写出字节数组
write(byte[] b),每次可以写出数组中的数据

写出指定长度字节数组
write(byte[] b, int off, int len) ,每次写出从off索引开始,len个字节

数据追加续写
这两个构造方法,参数中都需要传入一个boolean类型的值,true 表示追加数据,false 表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了

写出换行
Windows系统里,换行符号是\r\n
Unix系统里,每行结尾只有 换行 ,即\n;
Mac系统里,每行结尾是 回车 ,即\r。从 Mac OS X开始与Linux统一。


字节输入流【InputStream】

java.io.InputStream抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。


FileInputStream类

java.io.FileInputStream类是文件输入流,是InputStream的子类,从文件中读取字节。

构造方法

读取字节数据
read()方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1
使用字节数组读取:read(byte[] b),每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1



字符流
当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。


字符输入流【Reader】

java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。


FileReader类  
java.io.FileReader类是读取字符文件的便利类,是Reader的子类。构造时使用系统默认的字符编码和默认字节缓冲区。

构造方法

读取字符数据
read方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1

使用字符数组读取
read(char[] cbuf),每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回-1


字符输出流【Writer】

java.io.Writer抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。


FileWriter类
java.io.FileWriter类是写出字符到文件的便利类,是Writer的子类。构造时使用系统默认的字符编码和默认字节缓冲区。

基本写出数据
write(int b) 方法,每次可以写出一个字符数据

关闭和刷新
因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。

写出其他数据


属性集

java.util.Properties 继承于Hashtable ,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。该类也被许多Java类使用,比如获取系统属性时,System.getProperties 方法就是返回一个Properties对象。

构造方法
public Properties() :创建一个空的属性列表。

基本的存储方法

与流相关的方法
public void load(InputStream inStream): 从字节输入流中读取键值对。




day10

缓冲流

底层也是使用基本流(FileXxx)来读写
但缓冲流内部定义了一个缓冲数组, 在读的时候类似于我们一次读一个
数组的方式, 减少了磁盘操作次数, 提高了程序效率


缓冲字节输出流: BufferedOutputStream
java.io.BufferedOutputStream类: 缓冲字节输出流
构造方法:
BufferedOutputStream(OutputStream out): 使用基本流创建一个缓冲字节输出流
BufferedOutputStream(OutputStream out, int size): 使用基本流创建一个缓冲字节输出流, 设置缓冲区大小

缓冲字节输入流: BufferedInputStream
java.io.BufferedInputStream类: 缓冲字节输入流
// 构造方法
BufferedInputStream(InputStream in): 使用基本流创建一个缓冲字节输入流
BufferedInputStream(InputStream in, int size): 使用基本流创建一个缓冲字节输入流, 设置缓冲区大小

缓冲字符输出流: BufferedWriter
java.io.BufferedWriter类:
// 构造方法
BufferedWriter(Writer out): 使用基本流创建一个缓冲字符输出流
BufferedWriter(Writer out, int size): 使用基本流创建一个缓冲字符输出流, 设置缓冲区大小
// 特有方法
void newLine(): 写入一个换行符, 换行符自动根据当前系统确定

缓冲字符输入流: BufferedReader
java.io.BufferedReader类: 缓冲字符输入流
// 构造方法
BufferedReader(Reader in): 使用基本流创建一个缓冲字符输入流
BufferedReader(Reader in, int size): 使用基本流创建一个缓冲字符输入流, 设置缓冲区大小
// 特有方法
String readLine(): 一次读一行字符串, "不包含换行符". 读到文件末尾返回null


转换流

字符编码和字符集
编码: 字符 -> 字节  'a' -> 97
解码: 字节 -> 字符  97 -> 'a'

编码表:
字符和二进制数字的对应规则

字符集和编码表:
ASCII字符集
        ASCII编码表
        ASCII扩展编码表
ISO-8859-1字符集: Tomcat Web服务器程序
        Latin-1: 拉丁字符. 没有中文. 每个字符由1个byte组成
GB字符集
        GB2312编码表: 每个字符由2个byte组成
        GBK编码表: 每个字符由2个byte组成
        GB18030编码表: 每个字符由1, 2, 4个byte组成
Unicode字符集
        UTF-8: ASCII字符占1个byte, 拉丁字符占2个byte, 中文占3个byte, Unicode辅助字符占4个byte
        UTF-16
        UTF-32
ANSI: 表示使用系统默认编码表
OutputStreamWriter类
java.io.OutputStreamWriter类: 输出转换流. 字符流通往字节流的桥梁
// 构造方法
OutputStreamWriter(OutputStream out): 使用默认编码表创建转换流
OutputStreamWriter(OutputStream out, String charsetName): 使用指定编码表创建转换流


InputStreamReader类
java.io.InputStreamReader类: 输入转换流. 字节流通往字符流的桥梁
// 构造方法
InputStreamReader(InputStream in): 使用默认编码表创建转换流
InputStreamReader(InputStream in, String charsetName): 使用指定编码表创建转换流


序列化流

序列化: 内存中的对象转换为字节序列, 以流的方式写入到磁盘的文件中
对象 -> 字节
反序列化: 磁盘文件中的字节序列, 以流的方式读取到内存中变成对象
字节 -> 对象

通过序列化流, 我们可以将内存中的数据方便的存储到磁盘上, 下次程序启动后也能从磁盘读取恢复之前的对象状态


对象序列化流: ObjectOutputStream
java.io.ObjectOutputStream类: 对象字节输出流
// 构造方法
ObjectOutputStream(OutputStream out)
// 特有成员方法
void writeObject(Object obj): 将对象写出

注意:
被读写的对象的类必须实现"java.io.Serializable"接口,                  
否则会抛出"NotSerializableException"


对象反序列化流: ObjectInputStream
java.io.ObjectInputStream类: 对象字节输入流
// 构造方法
ObjectInputStream(InputStream in)
// 特有成员方法
Object readObject(): 读取对象

transient瞬态关键字: 避免属性序列化
static 修饰的成员变量属于类不属于对象, 所以不能序列化
transient 修饰的成员变量, 不能被序列化

transient 应用场景:
        如果对象的某个属性不希望被序列化, 可以使用 transient         
修饰, 这样就不会被对象流写到文件中


打印流

PrintStream特点:
1. 只有输出流, 没有输入流
2. PrintStream不会抛出IOException
3. 有特殊方法 print(), println(), 可以输出任意类型的值, 原样输


java.io.PrintStream类: 字节打印流
// 构造方法
PrintStream(File file): 创建字节打印流, 输出到一个文件
PrintStream(OutputStream out): 创建字节打印流, 输出到一个字节输出流
PrintStream(String fileName): 创建字节打印流, 输出到一个文件路径
        

java.lang.System类:
// 静态方法
static void setOut(PrintStream out): 设置System.out的输出目的地为参数的打印流










欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2