🗒️Java学习(下)

Agoinnnnnn

JavaSE|2024-4-20|Last edited: 2024-5-26|
type
status
date
slug
summary
tags
category
icon
password

2024.4.22

双列集合

特点:可以存入两个元素,称为键值对(键值对对象,Entry对象),键是不能重复的,但值是可以重复的。Map集合是双列集合中最顶级的一个集合。

Map中常见的API

方法名
说明
V put(K key,V value)
添加元素
V remove(Object key)
根据键删除键值对元素
void clear()
移除所有的键值对元素
boolean containsKey(Object key)
判断集合是否包含指定的键
boolean containsValue(Object value)
判断集合是否包含指定的值
boolean isEmpty()
判断集合是否为空
int size()
集合的长度,也就是集合中键值对的个数
put方法的细节:
在添加数据的时候,如果该键不存在,则直接把键值对对象添加到map集合中,方法返回null 在添加数据的时候,如果该键存在,那会把原先的键值对给覆盖,然后返回被覆盖的值
 

Map的遍历方式

1.通过键来找值
 
2.键值对对象来遍历
 
3.lambda表达式遍历
 

HashMap

特点:
  • 无序,不重复,无索引
  • HashMap是Map里面的一个实现类
  • 直接用Map里面的方法就可以了
  • 特点都是由键决定的:无序,不重复,无索引
  • HashMap和HashSet底层原理都是一模一样的,都是哈希表结构(利用键来计算哈希值,不和值有关)
 

LinkedHashMap

  • 有序,不重复,无索引
 

TreeMap

  • TreeMap和TreeSet底层原理是一致的,都是红黑树结构
  • 由键决定特性:不重复,无索引,可排序
  • 排序:对键来排序 默认是对键从小到大来进行排序,也可以自定义排序规则
两种排序规则
  • 实现comparable接口,指定比较规则
  • 创建集合时传递comparator比较器对象,指定比较规则
 

HashMap源码解析

 

TreeMap源码解析

一些问题(从黑马拷贝过来的)
 

可变参数

方法形参的个数可以发生变化的,格式:数据类型…名字 //int…args
方法的形参只能传递一个可变参数,且可变参数必须放在形参列表的最后面
 

collections集合工具类

常用api
 

2024.4.28

创建不可变集合

应用场景:
  • 如果某个数据不能被修改,那么把它防御性的拷贝到不可变集合中是个不错的选择
  • 当集合对象被不可信的库调用时,不可变形式是安全的
注意:这个集合不能添加,不能修改,不能删除
如果Map集合传入的键值对超过了十对:
简便的代码:
更简单代码(jdk10后出现)
 

Stream流

使用步骤:
  1. 得到一条stream流(流水线),并把数据放上去
  • Collection体系集合使用默认方法stream()生成流, default Stream<E>
  • stream()Map体系集合把Map转成Set集合,间接的生成流
  • 数组通过Arrays中的静态方法stream生成流
  • 同种数据类型的多个数据通过Stream接口的静态方法of(T… values)生成流
2.使用中间方法(方法调用完后还可以调用其他方法)来对流水线上的数据进行操作
方法名
说明
Stream<T> filter(Predicate predicate)
用于对流中的数据进行过滤
Stream<T> limit(long maxSize)
返回此流中的元素组成的流,截取前指定参数个数的数据
Stream<T> skip(long n)
跳过指定参数个数的数据,返回由该流的剩余元素组成的流
static <T> Stream<T> concat(Stream a, Stream b)
合并a和b两个流为一个流
Stream<T> distinct()
返回由该流的不同元素(根据Object.equals(Object) )组成的流
Stream<R>map(Function<T,R> mapper)
转换流中的数据
注意:修改stream流中的数据是不会影响到原来集合或者数组中的数据的
 
3.再使用终结方法(方法调用完后不能再调用其他方法)对流水线上的数据进行操作
方法名
说明
void forEach(Consumer action)
对此流的每个元素执行操作
long count()
统计
toArray()
收集流中的数据,放到数组中
collect(Collertor collertor)
收集流中的数据,放到集合中
 

2024.4.29

方法引用

把已经有的方法拿过来用,当作函数式接口中的抽象方法
方法引用的前提条件:
  1. 引用处必须是函数式接口
  1. 被引用的方法(可以是java自己写的,也可以是第三方写的)必须是存在的
  1. 被引用的形参和返回值需要和抽象方法一致
  1. 被引用的方法的功能要满足当前需求
 

引用静态方法

格式:类名::静态方法 Integer::parseInt
 

引用成员方法

对象::成员方法
  • 引用其他类的:其它类对象::方法名
  • 本类:this::方法名 //静态方法中无this
  • 父类:super::方法名 //静态方法中无super
 

引用构造方法

类名::new Student::new
 

使用类名引用成员方法

类名::成员方法 String::substring
独有的规则:
  • 被引用方法的形参,需要跟抽象方法的第二个形参到最后一个形参保持一致,返回值需要保持一致
抽象方法形参的详解:
  1. 第一个参数:表示被引用方法的调用者,决定了可以引用哪些类中的方法。在stream流中,第一个参数一般都表示流里的每一个数据。假设流里的数据是字符串,那么使用这种方式进行引用,就只能引用string这个类中的方法
  1. 第二个形参到最后一个形参:跟被引用方法的形参一致,如果没有第二个形参,说明被引用的方法需要的是无参的成员方法

引用数组的构造方法

数据类型[]::new

2024.5.8

异常(exception)

异常就表示代码可能出现的问题,我们通常会用exception以及他的子类来封装程序出现的问题。
运行时异常:RuntimeException及其子类,编译阶段不会出现异常提醒。会在运行时出现的异常(如:数组索引越界异常)
编译时异常:编译阶段就会出现异常提醒的(如:日期解析异常)
异常的作用:
1.用来查询bug的关键参考信息
2.一场可以作为方法内部的一种特殊返回值,以便通知调用者底层的执行情况
 

异常的处理方式

  1. jvm默认的处理方式
  • 把异常的名称,异常原因及异常出现的位置等信息输出在控制台
  • 程序停止运行,下面的代码不会再执行了
 
2.自己处理(捕获异常)
在System.out.println(arr[10]);这行代码出现问题后,会创建ArrayIndexOutOfBoundsException对象 然后会拿着这个对象和catch括号内的对象作比较,看括号内的变量是否可以接收这个对象 如果能被接收,那就说明这个异常被捕获了,然后就会执行catch里面对应的代码 当catch里的代码全部执行完后,就会继续执行try…catch体系以后的代码
重点:
  • 如果try中出现多个异常,那我们就要写多个catch来与之对应。但如果这些异常中有存在父子关系的话,那么父类一定要写在最下面。在jdk7以后,如果多个异常出现,并且执行相同的代码就可以
  • 如果try中遇到的问题没有被捕获,就相当于try…catch的代码白写了,还是直接由虚拟机来处理
  • 如果try中遇到了问题,那么try下面的代码就不会执行了,就会直接跳转到catch,来执行catch里面的语句
Throwable类中定义了一些查看方法:
  • public String getMessage():获取异常的描述信息,原因(提示给用户的时候,就提示错误原因。
  • public String toString():获取异常的类型和异常描述信息(不用)。
  • public void printStackTrace():打印异常的跟踪栈信息并输出到控制台。
3.抛出异常,给调用者处理
throws:写在方法定义处,表示声明一个异常,告诉调用者,使用本方法可能会有哪些异常
如果是编译时异常:必须要写 运行时异常:可以不写
throw:写在方法内,用来结束方法,手动抛出异常对象,交给调用者,方法中下面的代码就不再执行了

自定义异常

  1. 定义异常类
  1. 写继承关系,如果是运行时异常(表示由于参数错误而导致的问题)就继承runtimeexception,如果是编译时异常(提醒程序员检查本地信息)就直接继承exception
  1. 写空参构造
  1. 写带参构造

2024.5.13

File

File对象就表示一个路径,可以是文件的路径,也可以是文件夹的路径。这个路径可以是存在的也可以是不存在的。
常用方法:
创建删除功能的方法:
目录的遍历:
  • 当调用者file表示的路径不存在时,返回null
  • 当调用者file表示的时文件时,返回null
  • 当调用者file表示的路径是一个空文件夹时,返回一个长度为0的数组
  • 当调用者file表示的路径是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在一个数组中(如果该文件夹内有隐藏文件也会返回)
  • 当调用者file表示的路径是需要权限才能访问的文件夹时,会返回null

2024.5.15

IO流

存储和读取数据的解决方案,可以把程序中的数据保存到文件中(output),把本地文件中的数据加载到程序当中(input)。

1.1顶级父类:

输入流
输出流
输出流
字节流:可以操作所有类型的文件
字节输入流<br />InputStream
字节输出流<br />OutputStream
字符流:只能操作纯文本文件
字符输入流<br />Reader
字符输出流<br />Writer

2.1字节输出流【OutPutStream】

父类的共性方法:
每次完成流的操作后,必须调用close方法来释放系统资源

2.3 FileOutPutStream类(OutPutStream的子类)

步骤:
1.创建对象
细节:
  • 参数是字符串或者file对象都可以
  • 如果文件不存在则会直接创建一个文件,但必须保证父级路径是存在的
  • 如果文件已存在,则会清空文件里的内容
2.写入数据
细节:
  • write方法的参数是整数,但实际写入到文件内的内容是ascii码表中对应的字符
3.释放资源
每次使用完流之后必须释放资源(close方法)
写入数据的三种方式
换行写的方法:
在两个写出语句中再写出一个换行符即可
windows:\r\n
Linux: \n
mac: \r
续写的方法:
打开续写开关,是创建对象的第二个参数,默认是false表示关闭续写。
 

2.4 FileInPutStream类(InPutStream的子类)

操作本地文件的字节输入流,把本地文件的数据读取到程序中
步骤:
  1. 创建字节输入流对象
如果文件不存在则直接报错
2.读数据
一次只读一个字节,读出来的是ASCII码代表的数字
读到文件末尾,没有内容是会返回-1
3.释放资源
每次使用完都要释放资源
  • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
  • public abstract int read(): 从输入流读取数据的下一个字节。
  • public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
注意:必须要定义变量来接收read读取出来的数据,不能多次调用read方法。因为每调用一次read方法就会移动一次指针,多次调用就会使读取出来的数据不全。

2.5文件拷贝

方法很简单直接边读边写即可(这种方法适用于小文件拷贝,大文件拷贝如果用这种方法时间太慢了)(FileInPutStream一次只能读取一个字节)
public int read()
一次只能读一个字节
public int read(byte[] bytes)
一次能读一个字节数组数据

2.6 捕获异常处理

在前面学习io流都是直接抛出异常,但以后基本上要用try…catch来捕获异常了。但捕获异常的时候,可能会不能执行释放资源语句,这时就有一个需要学习的finally:
finally中的语句是必须会执行的,除非jvm退出,这样我们就可以把close这种必须要执行的代码放进去。
但是这时最基本的做法。
以下两种方法都必须让该类实现autocloseable接口,在jdk7时的简化方案:
jdk9:
 

2.7字符集

ASCII码表
编码规则:前面补0,补齐8位
notion image
 
GEK字符集
英文的存储兼容ASCII字符集
中文:
notion image
 
Unicode字符集
notion image
在UTF-8的编码规则下,中文是用三个字节来存储的
 
Java中编码和解码的代码:
编码的方法
public byte[] getBytes()
使用默认方式(UTF-8)进行编码
public byte[] getBytes(String charsetName)
使用指定方式进行编码
解码的方法
String(byte[] bytes)
使用默认方式(UTF-8)进行解码
String(byte[] bytes String charsetName)
使用指定方式进行解码
 

3.1 字符流

底层是字节流。输入时:一次读一个字节,遇到中文时,一次读多个字节(和字符集有关);输出时:底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中。非常好用于操作纯文本文件的时候

3.2 FileReader类(Reader的子类)

1.创建字符输入流对象:
2.读取数据
  1. 释放资源
close()

3.3 FileWriter类(Writer的子类)

1.创建FileWriter对象
2.写入数据
3.释放资源

2024.5.18

扩展点:
  • FileReader在读取的时候会先把数据放在一个长度为8192的字节数组的缓冲区中,再去进行读取,如果文件长度超过了8192,那就会再去文件中读取剩余的内容。
  • FileWriter在写入的时候,会先把数据写到缓冲区中。有三种情况会将缓冲区内的内容写入到文件中:第一种是当缓冲区被装满了的时候,会将缓冲区里的内容写入到文件内;第二种是调用flash方法,会将已写入的内容写入到文件内,但还可以继续写入;第三种就是调用close方法来关流,就可以写入到文件内。此时就不能继续写入了

1.缓冲流

  • 字节缓冲流BufferedInputStreamBufferedOutputStream
  • 字符缓冲流BufferedReaderBufferedWriter

1.1字节缓冲流

缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小(8192字节)的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

1.2字符缓冲流

因为原来的FileReader和FilrWriter都是有缓冲流的,所以字符缓冲流对效率的提升并不是很大,但他有两个独有的方法,这是非常重要的
特有的方法:
🥰
  • BufferedReader:public String readLine(): 读一行文字。
  • BufferedWriter:public void newLine(): 写一行行分隔符,由系统属性定义符号。

2.转换流

字符流和字节流之间的桥梁
作用1:指定字符集读写(jkd11后淘汰了)。可以使用Filewriter或者filereader的构造方法中都可以传入字符的编码方式,
作用2:字节流想使用字符流的方法,那就可以用转换流转一下。

2.1 InputStreamReader类

构造方法:

2.2 OutputStreamWriter类

构造方法:

3.序列化流和反序列化流

Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据对象的类型对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化对象的数据对象的类型对象中存储的数据信息,都可以用来在内存中创建对象。

3.1 序列化流ObjectOutputStream类

使用对象输出流写入对象的时候会出现NotSerializableException异常,这时我们就需要让JavaBean类实现Serializable接口。
Serializable接口中是没有抽象方法的,这是一个标记接口,只有实现这个接口才能说明当前对象能被序列化。

3.2 反序列化流ObjectInputStream类

3.3 序列化流和反序列化流的一些注意事项

提前了解的知识:
  • 当JavaBean类实现了serializable接口后会根据JavaBean类内的内容来生成一个版本号。在实现反序列化流的时候是根据这个版本号来转化对象的,如果此时JavaBean类的版本号和序列化流后文件内对象的版本号不同的话就会报错
1.当写入数据后,由于业务需求需要更改JavaBean类,此时版本号会发生更改。解决方法就是在JavaBean类内把版本号给固定:
2.如果不想把JavaBean类内的某个变量序列化到文件当中,可以在变量前加transient瞬态关键字,能够不让变量序列化

4.打印流

平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
  • 指:PrintStream和PrintWriter两个类
  • 打印流只能操作文件目的地,不能操作数据源
  • 特有的写出方法可以实现数据原样写出
  • 特有的写出方法,可以实现自动刷新,自动换行

4.1 PrintStream类

 
成员方法:

4.2 PrintWriter类

构造方法和PrintStream类基本一致
成员方法也差不多一样。
PrintWriter底层有缓冲区,如果想要自动刷新必须手动开启

4.3 打印流的使用场景

System.out.println();==PrintStream ps1 = System.out; ps1.println(151);
获取打印流对象,该打印流在虚拟机启动的时候就由虚拟机创建了,默认指向控制台

5.解压缩流/压缩流

在学习解压缩流的时候遇到了一个教学视频上没有的报错,去查了一下发现了解决方法
notion image
在循环打印文件名到打印完后的时候,并没有打印出null来显示不存在文件了,而是出现了报错。
🥰
如果在Java解压文件时报错 “malformed”,通常表示压缩文件的格式不正确或者损坏。这里只说了一种解决方法:是编码问题,加上GBK编码格式后即可正确进行,程序不会报错,并且文件也可以正确解压啦。

5.1 解压缩流

5.2 压缩流

2024.5.19

Commons-io

Commons是apache开源基金组织提供的工具包,里面有很多帮助我们提高开发效率的API
DateUtil 日期时间工具类
TimeInterval 计时器工具类
StrUtil 字符串工具类
HexUtil 16进制工具类
HashUtil Hash算法类
ObjectUtil 对象工具类
ReflectUtil 反射工具类
TypeUtil 泛型类型工具类
PageUtil 分页工具类
NumberUtil 数字工具类
使用方式:
1,新建lib文件夹
2,把第三方jar包粘贴到文件夹中
3,右键点击add as a library
相关方法在另一篇博客啦

Hutool

Hutool是国人开发的开源工具包,里面有很多帮助我们提高开发效率的API
DateUtil 日期时间工具类
TimeInterval 计时器工具类
StrUtil 字符串工具类
HexUtil 16进制工具类
HashUtil Hash算法类
ObjectUtil 对象工具类
ReflectUtil 反射工具类
TypeUtil 泛型类型工具类
PageUtil 分页工具类
NumberUtil 数字工具类
使用方式:
1,新建lib文件夹
2,把第三方jar包粘贴到文件夹中
3,右键点击add as a library

2024.5.21

properties配置文件

以键值对的方式在存储配置信息,左边是键,右边是值。文件后缀名就是properties。
properties是一个双列集合,拥有map集合所有的特点。
有一些特别的方法,可以把集合中的数据,按照键值对的形式写到配置文件中。也可以把配置文件中的信息,读取到集合中来。

2024.5.22

1.实现多线程

1.1线程与进程

线程:线程是操作系统中能够进行运算调度的最小单位。它被包含在进程(进程是程序的基本执行实体)之中,是进程中的实际运作单位。
简单理解就是:应用软件中,可以同时运行的功能

1.2并发和并行

  • 并行:在同一时刻,有多个指令在多个CPU上同时执行。
  • 并发:在同一时刻,有多个指令在单个CPU上交替执行。

1.3 实现方式1:继承Thread类的方式实现

方法名
说明
void run()
在线程开启后,此方法将被调用执行
void start()
使此线程开始执行,Java虚拟机会调用run方法()
实现步骤:
  • 定义一个类MyThread继承Thread类
  • 在MyThread类中重写run()方法
  • 创建MyThread类的对象
  • 启动线程

1.4 实现方式2:实现Runnable接口的方式进行实现

方法名
说明
Thread(Runnable target)
分配一个新的Thread对象
Thread(Runnable target, String name)
分配一个新的Thread对象
  • 实现步骤
    • 定义一个类MyRunnable实现Runnable接口
    • 在MyRunnable类中重写run()方法
    • 创建MyRunnable类的对象
    • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
    • 启动线程

1.5 实现方式3:实现Callable接口和Future接口的方式进行实现

方法名
说明
V call()
计算结果,如果无法计算结果,则抛出一个异常
FutureTask(Callable<V> callable)
创建一个 FutureTask,一旦运行就执行给定的 Callable
V get()
如有必要,等待计算完成,然后获取其结果
  • 实现步骤
    • 定义一个类MyCallable实现Callable接口
    • 在MyCallable类中重写call()方法
    • 创建MyCallable类的对象
    • 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
    • 创建Thread类的对象,把FutureTask对象作为构造方法的参数
    • 启动线程
    • 再调用get方法,就可以获取线程结束之后的结果。
 
  • 三种实现方式的对比
    • 实现Runnable、Callable接口
      • 好处: 扩展性强,实现该接口的同时还可以继承其他的类
      • 缺点: 编程相对复杂,不能直接使用Thread类中的方法
    • 继承Thread类
      • 好处: 编程比较简单,可以直接使用Thread类中的方法
      • 缺点: 可以扩展性较差,不能再继承其他的类

2.多线程常见的成员方法

2.1简单方法

方法名
说明
void setName(String name)
将此线程的名称更改为等于参数name
String getName()
返回此线程的名称
Thread currentThread()
返回对当前正在执行的线程对象的引用
static void sleep(long millis)
使当前正在执行的线程停留(暂停执行)指定的毫秒数
  • 如果没有给线程设置名字,那么线程的默认名字是:Thread-序号
  • 如果我们要给线程设置名字,可以使用setName方法或者使用构造方法。但是使用构造方法的时候需要自己在类中写构造方法

2.2线程优先级

线程调度
  • 两种调度方式
    • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
    • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
  • Java使用的是抢占式调度模型
方法名
说明
final int getPriority()
返回此线程的优先级(分为十档,默认是5档)
final void setPriority(int newPriority)
更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10

2.3守护线程

方法名
说明
void setDaemon(boolean on)
将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
当其他非守护线程执行结束后,守护线程就会陆续结束

2.4出让线程和插入线程

方法名
说明
public static void yield()
出让线程
public final void join()
插入线程

2.5同步代码块

2.6同步方法

直接把synchronized关键字加到方法上
特点:
  1. 同步方法是把方法内所有的代码都锁定
  1. 锁对象不能自己指定:非静态:this;静态:当前类的字节码文件

2.7 Lock锁

新锁对象lock:lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
  • ReentrantLock构造方法
    • 方法名
      说明
      ReentrantLock()
      创建一个ReentrantLock的实例
  • 加锁解锁方法
    • 方法名
      说明
      void lock()
      获得锁
      void unlock()
      释放锁

2.8死锁

  • 概述
    • 线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
  • 什么情况下会产生死锁
      1. 资源有限
      1. 同步嵌套

2024.5.23

3.生产者和消费者(等待唤醒机制)

3.1一种多线程协作的模式:其中包含两类线程

  • 一类是生产者线程用于生产数据
  • 一类是消费者线程用于消费数据
生产者和消费者的关系通常用一个共享的数据区域来表示,可以理解为生产者生产数据之后把数据放进该区域中,并且不关心消费者的行为;消费者则去该区域获取消费数据,也不需要关心生产者的行为。当该区域无数据时,此时消费者又抢夺cpu进去了,那么则需要等待生产者生产数据放进去之后唤醒消费者来获取数据。但如果此时区域内有数据了,并且是生产者抢夺到了cpu进去了,则此时生产者就需要等待消费者进来获取数据当消费者获取完数据之后就会唤醒生产者继续去生产数据。这个过程就是等待唤醒机制
Object类的等待和唤醒方法:
方法名
说明
void wait()
导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify()
唤醒正在等待对象监视器的单个线程
void notifyAll()
唤醒正在等待对象监视器的所有线程

3.2阻塞队列方式

  • 常见BlockingQueue(实现类接口):
    • ArrayBlockingQueue: 底层是数组,有界
      LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值
  • BlockingQueue的核心方法:
    • put(anObject): 将参数放入队列,如果放不进去会阻塞
      take(): 取出第一个数据,取不到会阻塞

3.3线程的六大状态

notion image
运行状态是没有定义的

4.线程池

4.1线程池核心原理

  1. 创建一个池子,池子中是空的
  1. 提交任务的时候,会在池子中创建一个新的线程对象,执行完任务后,线程会归还给池子,下回再次提交任务的时候,就不需要创建新的线程了,直接复用已有的线程即可
  1. 但如果提交任务的时候,池子中没有多的线程了,也无法创建新的线程了,任务就会排队等待
线程池的设计思路 :
  1. 准备一个任务容器
  1. 一次性启动多个(2个)消费者线程
  1. 刚开始任务容器是空的,所以线程都在wait
  1. 直到一个外部线程向这个任务容器中扔了一个"任务",就会有一个消费者线程被唤醒
  1. 这个消费者线程取出"任务",并且执行这个任务,执行完毕后,继续等待下一次任务的到来

4.2线程池-Executors默认线程池

4.3线程池-ThreadPoolExecutor自定义线程池

三个临界点:
  • 当核心线程满时,再提交任务就会进入队列排队
  • 当核心线程满时,并且队伍满时,才会创建临时线程
  • 当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略

4.4线程池-非默认任务拒绝策略

4.5定义多大的线程池

两种计算方法:
  • CPU密集型运算:最大并行数+1 //计算类的较多,文件读取和写入较少就用这个
  • I/O密集型运算:最大并行数*期望CPU利用率*总时间(CPU计算时间+等待时间)/CPU计算时间 //读取本地文件或数据库较多,就用这个

2024.5.25

1.网络编程

1.1 初识

计算机和计算机通过网络进行数据传输
常见软件架构:
  • C/S:Client/Server 客户端/服务器
需要用户本地下载并安装客户端程序,在远程有个服务器端程序
  • B/S:Browser/Server 浏览器/服务器
只需要一个浏览器,用户通过不同的网址访问不同的服务器

1.2 三要素

  • IP地址
    • 要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识
  • 端口
    • 网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识
  • 协议
    • 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议

1.3 IP

InetAddress:此类表示Internet协议(IP)地址
  • 相关方法
    • 方法名
      说明
      static InetAddress getByName(String host)
      确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
      String getHostName()
      获取此IP地址的主机名
      String getHostAddress()
      返回文本显示中的IP地址字符串

1.4 端口号

  • 端口
    • 设备上应用程序的唯一标识
  • 端口号
    • 用两个字节表示的整数,它的取值范围是0~65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败

1.5 协议

计算机网络中,连接和通信的规则被称为网络通信协议
notion image
UDP协议:
  • 用户数据报协议
  • UDP是面向无连接通信(不管要发送的电脑是否连接网络,就直接发送)协议。速度快,有大小限制,一次最多发送64k,数据不安全,易丢失数据
TCP协议:
  • 传输控制协议
  • TCP协议是面向链接的通信协议。速度慢,没有大小限制,数据安全

1.6 UDP协议发送接收数据(单播)

  • 构造方法
    • 方法名
      说明
      DatagramSocket()
      创建数据报套接字并将其绑定到本机地址上的任何可用端口
      DatagramPacket(byte[] buf,int len,InetAddress add,int port)
      创建数据包,发送长度为len的数据包到指定主机的指定端口
  • 相关方法
    • 方法名
      说明
      void send(DatagramPacket p)
      发送数据报包
      void close()
      关闭数据报套接字
      void receive(DatagramPacket p)
      从此套接字接受数据报包
      byte[] getData()
      返回数据缓冲区
      int getLength()
      返回要发送的数据的长度或接收的数据的长度
  • 发送数据的步骤
    • 创建发送端的Socket对象(DatagramSocket)
    • 创建数据,并把数据打包
    • 调用DatagramSocket对象的方法发送数据
    • 关闭发送端

1.7 组播

一台电脑发送,一组电脑接收

1.8 广播

只需要把单播代码里的发送的主机地址改成255.255.255.255就可以发送给该局域网中所有的主机

1.9 UDP协议发送接收数据

发送:
  • 构造方法
    • 方法名
      说明
      Socket(InetAddress address,int port)
      创建流套接字并将其连接到指定IP指定端口号
      Socket(String host, int port)
      创建流套接字并将其连接到指定主机上的指定端口号
  • 相关方法
    • 方法名
      说明
      InputStream getInputStream()
      返回此套接字的输入流
      OutputStream getOutputStream()
      返回此套接字的输出流
接收:
  • 构造方法
    • 方法名
      说明
      ServletSocket(int port)
      创建绑定到指定端口的服务器套接字
  • 相关方法
    • 方法名
      说明
      Socket accept()
      监听要连接到此的套接字并接受它

1.10 TCP协议(三次握手)

notion image

1.11 TCP协议(四次挥手)

notion image

2024.5.26

1.反射

反射允许对成员变量,成员方法,构造方法的信息进行编程访问(也就是将它们获取出来,并且能把他们所有的信息给获取出来,并可以运行或更改)
反射都是从class字节码文件中获取的内容。

1.1 获取class对象的三种方式

  • Class.foeName(”全类名”); //全类名→包名加类名
  • 类名.class
  • 对象.getClass();
 

1.2 利用反射去获取构造方法

class类中:
方法名
说明
Constructor<?>[] getConstructors()
获得所有的构造(只能public修饰)
Constructor<?>[] getDeclaredConstructors()
获得所有的构造(包含private修饰)
Constructor<T> getConstructor(Class<?>… parameterTypes)
获取指定构造(只能public修饰)
Constructor<T> getDeclaredConstructor(Class<?>… parameterTypes)
获取指定构造(包含private修饰)
获取单个构造方法的时候,会需要在getDeclaredConstructor()中传递该构造方法中的参数的字节码文件。比如int age就需要传递int.class
不同修饰符返回的值不同:
notion image

1.3 用反射去获取成员变量

方法名
说明
Field[] getFields()
返回所有成员变量对象的数组(只能拿public的)
Field[] getDeclaredFields()
返回所有成员变量对象的数组,存在就能拿到
Field getField(String name)
返回单个成员变量对象(只能拿public的)
Field getDeclaredField(String name)
返回单个成员变量对象,存在就能拿到
获取成员变量并获取值和修改值
方法
说明
void set(Object obj, Object value)
赋值
Object get(Object obj)
获取值

1.4 用反射去获取成员方法

方法名
说明
Method[] getMethods()
返回所有成员方法对象的数组(只能拿public的)可以获得到父类中的公共方法
Method[] getDeclaredMethods()
返回所有成员方法对象的数组,存在就能拿到,但不能获取到父类中的方法
Method getMethod(String name, Class<?>… parameterTypes)
返回单个成员方法对象(只能拿public的),需要传入参数的类型,防止方法的重载。
Method getDeclaredMethod(String name, Class<?>…
返回单个成员方法对象(private也可以)
方法运行:

1.5 结合配置文件动态创建对象并调用方法

1.6 总结

get
获取
set
设置
Constructor
构造方法
Parameter
参数
Field
成员变量
Modifiers
权限修饰符
Method
成员方法
Declared
私有的

2.动态代理

可以无侵入式的给代码怎加额外的功能,当对象内的方法太多了就可以通过代理来转移部分职责。当对象有什么方法像被代理时,代理就一定要有对应的方法。将要代理的方法全部写在一个接口中,对象和代理都必须实现该接口。

2.1为Java对象创建一个代理对象

  • java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法
创建代理对象,注释中的需求就是test类中要实现的代码

3.日志

利用log日志可以把这些详细信息保存到文件和数据库中。
跟输出语句一样,可以把程序在运行过程中的详细信息都打印在控制台上。
利用log日志还可以把这些详细信息保存到文件和数据库中。
是第三方提供的东西,需要导入jar包。

3.1 体系结构

日志规范接口:
  • Commons Logging //简称JCL
  • Simple Logging Facade for Java //简称slf4j
日志常见的实现框架:
  • Log4J
  • Logback(重点)

3.2 Logback日志框架

获取对象:

3.3 日志级别

控制系统中哪些日志级别是可以输出的,只输出级别不低于设定级别的日志信息
还有两个特殊的:
ALL:输出所有日志
OFF:关闭所有日志
日志级别从小到大的关系:
TRACE < DEBUG < INFO < WARN < ERROR

4.类加载器

将.class文件加载到内存中
类加载的时机:
  • 创建类的实例(对象)
  • 调用类的方法
  • 访问类或接口的类变量,或者为该类变量赋值
  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
  • 初始化某个类的子类
  • 直接用Java.exe命令来运行某个主类

4.1 类加载的过程

1.加载:
  • 通过包名+类名,获取这个类,准备用流来进行传输
  • 将这个类加载到内存中
  • 加载完毕创建一个class对象来存储
2.链接
  • 验证:文件中的信息是否符合虚拟机的规范,有没有安全隐患
  • 准备:初始化静态变量
  • 解析:本类中运用到了其他类,此时就要去找对应的类
3.初始化
根据程序员通过程序制定的主观计划去初始化类变量和其他资源
(静态变量赋值以及初始化其他资源)

4.2 类加载器的分类

  • Bootstrap classloader:虚拟机的内置类加载器,通常表示为null ,并且没有父null
  • Platform classloader:平台类加载器,负责加载JDK中一些特殊的模块
  • System classloader:系统类加载器,负责加载用户类路径上所指定的类库

4.3 双亲委派模型

bc→pc→sc→自定义加载器
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式

4.4 Classloader一些方法

方法名
说明
public static ClassLoader getSystemClassLoader()
获取系统类加载器
public InputStream getResourceAsStream(String name)
加载某一个资源文件

5.XML

用作配置文件。是一种可扩展(标签的名字可以自己定义)的标记语言(通过标签来描述数据的一门语言,<>内的东西)
三种配置文件的优缺点:
  • TXT文件:优点:没有优点;缺点:不利于阅读
  • properties文件:优点:键值对形式易于阅读,解析简单;缺点:无法配置一组一组的数据
  • XML文件:优点:易于阅读,可以配置成组出现的数据;缺点:解析复杂

5.1 xml的基本语法

后缀名必须为xml
  • 标签由一对尖括号和合法标识符组成
    • 标签必须成对出现
      • 特殊的标签可以不成对,但是必须有结束标记
        • 标签中可以定义属性,属性和标签名空格隔开,属性值必须用引号引起来
          • 标签需要正确的嵌套
          • 注释
          • XML文件中可以存在以下特殊字符,要想使用得用前面的那些字符
          • XML文件中可以存在CDATA区
            • <![CDATA[ …内容… ]]> 在该区域中所写的代码都不含特殊含义

          5.2 dtd约束

          编写DTD约束
          • 步骤
              1. 创建一个文件,这个文件的后缀名为.dtd
              1. 看xml文件中使用了哪些元素
                1. <!ELEMENT> 可以定义元素
              1. 判断元素是简单元素还是复杂元素
                1. 简单元素:没有子元素。 复杂元素:有子元素的元素;
          • 代码实现
            引入DTD约束
            • 引入DTD约束的三种方法
              • 引入本地dtd
                • <!DOCTYPE 根元素名称 SYSTEM ‘DTD文件的路径’>
              • 在xml文件内部引入
                • <!DOCTYPE 根元素名称 [ dtd文件内容 ]>
              • 引入网络dtd
                • <!DOCTYPE 根元素的名称 PUBLIC “DTD文件名称” “DTD文档的URL”>

            5.3 schema约束

            schema和dtd的区别
            1. schema约束文件也是一个xml文件,符合xml的语法,这个文件的后缀名.xsd
            1. 一个xml中可以引用多个schema约束文件,多个schema使用名称空间区分(名称空间类似于java包名)
            1. dtd里面元素类型的取值比较单一常见的是PCDATA类型,但是在schema里面可以支持很多个数据类型
            1. schema 语法更加的复杂
            编写schema约束
            • 步骤
              • 1,创建一个文件,这个文件的后缀名为.xsd。
                2,定义文档声明
                3,schema文件的根标签为: <schema>
                4,在<schema>中定义属性: xmlns=http://www.w3.org/2001/XMLSchema //schema文件被这个网址中的xml文件约束着
                5,在<schema>中定义属性 : targetNamespace =唯一的url地址,指定当前这个schema文件的名称空间。
                6,在<schema>中定义属性 : elementFormDefault="qualified“,表示当前schema文件是一个质量良好的文件。
                7,通过element定义元素
                8,判断当前元素是简单元素还是复杂元素
            引入schema约束
            • 步骤
              • 1,在根标签上定义属性xmlns=“http://www.w3.org/2001/XMLSchema-instance” 2,通过xmlns引入约束文件的名称空间
                3,给某一个xmlns属性添加一个标识,用于区分不同的名称空间 格式为: xmlns:标识=“名称空间地址” ,标识可以是任意的,但是一般取值都是xsi
                4,通过xsi:schemaLocation指定名称空间所对应的约束文件路径 格式为:xsi:schemaLocation = "名称空间url 文件路径“

            5.4 xml的解析

            有两种解析方式:SAX解析和DOM解析,现在基本使用DOM解析(将xml文件全部读取到内存中,形成父级结构)。

            5.5 dom4j解析xml文件

            到官网下载dom4j,并导包到模块中就行啦。
            • SAXReader类:
            public SAXReader()
            创建dom4j的解析器对象
            Document read(File file)
            加载xml文件成为Document对象
            • Document类
            Element getRootElement()
            获得根元素对象
            • Element类
            List<Element> elements()
            得到当前元素下的所有子元素
            List<Element> elements(String name)
            得到当前元素下指定名字的子元素返回集合
            Element element(String name)
            得到当前元素下指定名字的子元素,如果有很多名字相同的则返回第一个
            String getName()
            得到元素名字
            String attributeValue(String name)
            通过属性名直接得到属性值
            String elementText(子元素名)
            得到指定名称的子元素的文本
            String Text()
            得到文本

            5.6 xml检索技术:Xpath

            从xml里面只需要一个信息的时候就使用Xpath,使用路径表达式来定位xml文件中的元素节点或属性。
            也要导包:jaxen.jar
            Document类中Xpath的API:
            Node selectSingleNode(”表达式”)
            获取符合表达式的唯一元素。如果有重名的则返回第一个获取到的
            List<Node> selectNodes(”表达式”)
            获取符合表达式的元素集合
            表达式(检索方案):
            • 绝对路径 /根元素/子元素/孙元素 不能跨级
            • 相对路径 ./子元素/孙元素 不能跨级(需要相对于自身去查找,可以用Document对象也可以用到Element对象)
            • 全文检索
            • 属性查找

            6.单元测试

            针对Java方法(Java程序最小的功能单元)的测试,进而检查方法的正确性

            6.1 JUnit单元测试框架

            可以直接在idea中直接使用
            JUnit的优点:
            • 可以灵活的选择要测试哪些方法,也可以一键测试所有的方法
            • 单元测试中的某个方法出错了,不会影响到其他正在测试的方法
            • 运行成功是绿色,失败是红色

            6.2 使用JUnit

            被测试的方法必须是公共的无参数无返回值的非静态方法
            直接在方法上面写上@Test注解来表明这是个测试方法,写完注解后会自动导包,如果没导包就得自己导。
            以后在实际开发当中,如果想要测试一个方法是否正确,并不是直接在该方法上加上@Test的 而是自己独立编写一个测试类。(不要写main方法) 在这个类中编写一些方法,在这些方法里面去调用那个要测试的类
            在测试方法运行的实际结果和预期结果是否相等时,我们可以使用Assert.assertEquals()方法来判断,该方法的第一个参数是当两个结果不相等时,返回的信息语句。第二个参数是预期结果,第三个参数是实际结果。
            常用注解:
            @Test
            测试方法
            @Before
            用来修饰实例方法,该方法会在每一个测试方法执行前执行一次
            @After
            用来修饰实例方法,该方法会在每一个测试方法执行后执行一次
            @BeforeClass
            用来修饰静态方法,该方法会在每一个测试方法执行前执行一次
            @AfterClass
            用来修饰静态方法,该方法会在每一个测试方法执行后执行一次
            在实际开发中我们是不能对测试的数据进行污染的所以我们就得: 1.利用Before注解对数据进行初始化 2.利用Test去测试方法 3.利用After注解对数据进行还原
            扩展点:
            在单元测试中,相对路径是相对于模块而言的

            7.注解

            注解是给jvm和编译器看的
            @Deprecated注解是表示该方法已过时,可以继续使用,但一定有可以替代的方法
            @SuppressWarnings(”all”)注解:压制警告

            7.1 自定义注解

            7.2 元注解

            可以写在注解上面的注解
            @Target :指定注解能在哪里使用
            @Retention :可以理解为保留时间(生命周期)

            Target:

            作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。
            可使用的值定义在ElementType枚举类中,常用值如下
            • TYPE,类,接口
            • FIELD, 成员变量
            • METHOD, 成员方法
            • PARAMETER, 方法参数
            • CONSTRUCTOR, 构造方法
            • LOCAL_VARIABLE, 局部变量

            Retention:

            作用:用来标识注解的生命周期(有效范围)
            可使用的值定义在RetentionPolicy枚举类中,常用值如下
            • SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
            • CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
            • RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)