集合
集合按照其存储结构可以分为两大类,分别是单列集合java.util.Collection
和双列集合java.util.Map
Collection集合
Collection:单列集合类的根接口,有两个重要的子接口,分别是java.util.List
和java.util.Set
List
:特点是元素有序、元素可重复,主要实现类有数组结构的java.util.ArrayList
和双向链表结构的java.util.LinkedList
Set
:特点是元素无序,而且不可重复,主要实现类有采用数组+链表+红黑树实现的java.util.HashSet
和java.util.TreeSet
。在HashSe
t下面有一个子类 java.util.LinkedHashSet
,它是链表和哈希表组合的一个数据存储结构,有序。
应用场景
想保证插入元素的唯一性,也就是不想有重复值出现,可以选择Set
实现类
HashSet集合存储数据的结构
Iterator迭代器
1.迭代:在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代
public class IteratorDemo {
public static void main(String[] args) {
// 使用多态方式 创建对象
Collection<String> coll = new ArrayList<String>();
// 添加元素到集合
coll.add("串串星人");
coll.add("吐槽星人");
coll.add("汪星人");
//遍历
//使用迭代器 遍历 每个集合对象都有自己的迭代器
Iterator<String> it = coll.iterator();
// 泛型指的是 迭代出 元素的数据类型
while(it.hasNext()){ //判断是否有迭代元素
String s = it.next();//获取迭代出的元素
System.out.println(s);
}
}
}
泛型
好处
- 将运行时期的ClassCastException,转移到了编译时期变成了编译失败
- 避免了类型强转的麻烦
泛型通配符:<?>
tips:泛型不存在继承关系 Collection< Object> list = new ArrayList< String>();这种是错误的
泛型的上限:
- 格式:
类型名称 <? extends 类 > 对象名称
- 意义:
只能接收该类型及其子类
泛型的下限:
- 格式:
类型名称 <? super 类 > 对象名称
- 意义:
只能接收该类型及其父类型
Collections集合工具类
用于对集合进行操作,添加元素,乱序,排序等
Map接口
常用集合
HashMap:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
LinkedHashMap:HashMap下的子类,存储数据采用的哈希表结构+链表结构。通过链 表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复。
Debug
使用IDEA的断点调试功能,查看程序的运行过程
- 在有效代码行,点击行号右边的空白区域,设置断点,程序执行到断点将停止,右键打开debug窗口
异常
指的是程序在执行过程中,出现的非正常的情况,终会导致JVM的非正常停止。 在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理
图解
线程与同步
1.每个执行线程都有自己独立的栈
同步代码块
synchronized
关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问
同步锁
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
- 锁对象 可以是任意类型。
- 多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着 (BLOCKED)。
例
public class Ticket implements Runnable {
private int ticket = 100;
Object lock = new Object();
@Override
public void run() {
//每个窗口卖票的操作
// 窗口 永远开启
synchronized (lock){
if(ticket>0){//有票,可卖
try{//出票操作
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
同步方法
同步方法:使用synchronized
修饰的方法,保证A线程执行该方法的时候,其他线程只能在方法外等着
Lock锁
public class Ticket implements Runnable {
private int ticket = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
//每个窗口卖票的操作
// 窗口 永远开启
while (true) {
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.unlock();
}
}
}
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可 运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操 作系统处理器。 |
Blocked(锁阻 塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状 态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限 等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个 状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting(计时 等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态 将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、 Object.wait。 |
Teminated(被 终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
调用wait和notify方法需要注意的细节
- wait方法与notify方法必须要由同一个锁对象调用
- wait方法与notify方法是属于Object类的方法的
- wait方法与notify方法必须要在同步代码块或者是同步函数中使用
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200606211929460.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzYzODYxNA==,size_16,color_FFFFFF,t_70
例
Lock类
package com.aa.test.useThread;
public class Oats {
String mate;
boolean flag=false;
}
宝宝线程类
package com.aa.test.useThread;
public class Baby extends Thread {
private Oats oats;
public Baby(String name, Oats oats) {
super(name);
this.oats = oats;
}
@Override
public void run() {
while (true) {
synchronized (oats) {
if (oats.flag == false) {
try {
oats.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.getName() + "在吃" + oats.mate + "燕麦");
oats.flag = false;
oats.notify();
}
}
}
}
厨师线程类
package com.aa.test.useThread;
public class Chef extends Thread {
private Oats oats;
public Chef(String name, Oats oats) {
super(name);
this.oats = oats;
}
@Override
public void run() {
int count=6;
while (count>0) {
synchronized (oats){
if (oats.flag == true) {
try {
oats.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(count%2==0){
oats.mate="牛奶";
}
else {
oats.mate="豆乳";
}
System.out.println(this.getName() + "在做美味的" + oats.mate + "燕麦");
System.out.println(oats.mate + "燕麦做好啦");
count--;
oats.flag = true;
System.out.println("快来吃"+oats.mate+"燕麦");
oats.notify();
}
}
}
}
测试类
package com.aa.test.useThread;
public class Test {
public static void main(String[] args) {
Oats oats = new Oats();
Chef chef = new Chef("厨师", oats);
Baby baby = new Baby("宝宝", oats);
chef.start();
baby.start();
}
}
效果
Lambda表达式
例
public class Demo02LambdaRunnable {
public static void main(String[] args) {
new Thread(() ‐ > System.out.println("多线程任务执行!")).start(); // 启动线程
}
}
核心代码
() ‐ > System.out.println("多线程任务执行!")
- 前面的一对小括号即 run 方法的参数(无),代表不需要任何条件;
- 中间的一个箭头代表将前面的参数传递给后面的代码;
- 后面的输出语句即业务逻辑代码
格式
(参数类型 参数名称) ‐> { 代码语句 }
例子
1.有参数和返回值时
计算器接口
package com.aa.test.Lambda;
public interface Calculator {
int calc(int a,int b);
}
实现
package com.aa.test.Lambda;
public class Test {
public static void main(String[] args) {
invokeCalc(120,130,(int a,int b)->{//只有一条语句时,{}可省略
//如果只有一个变量,()可省略
return a+b;
});
}
private static void invokeCalc(int a,int b,Calculator calculator){
int result=calculator.calc(a,b);
System.out.println("结果是"+result);
}
}
使用前提
- 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。 无论是JDK内置的
Runnable
、Comparator
接口还是自定义的接口,只有当接口中的抽象方法存在且唯一 时,才可以使用Lambda。 - 使用Lambda必须具有上下文推断。 也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
备注:有且仅有一个抽象方法的接口,称为“函数式接口”
流
InputSteam字节流和Reader字符流
- | 输入流 | 输出流 |
---|---|---|
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
当使用FileInputStream
读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为 一个中文字符可能占用多个字节存储,所以有字符流,专用于处理文本文件。
- 字符流:以字符为单位读写数据,专门用于处理文本文件,只能操作文本文件
注意:
FlieWriter
关闭资源时,与FileOutputStream
不同.如果不关闭,数据只是保存到缓冲区,并未保存到文件。
因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据 的。如果我们既想写出数据,又想继续使用流,就需要 flush 方法。
flush :刷新缓冲区,流对象可以继续使用。
close :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
Properties 属性类
public Object setProperty(String key, String value)
: 保存一对属性。
public String getProperty(String key)
:使用此属性列表中指定的键搜索属性值。
public Set<String> stringPropertyNames()
:所有键的名称的集合
BufferedInputStream字节缓冲流和 BufferedReader字符缓冲流
在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO 次数,从而提高读写的效率。
缓冲流读写方法与基本的流一致
字符缓冲流的特有方法
字符缓冲流的基本方法与普通字符流调用方式一致
public String readLine()
: 读一行文字
public void newLine()
: 写一行行分隔符,由系统属性定义符号
InputStreamReader转换流
是Reader
的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
InputStreamReader(InputStream in)
: 创建一个使用默认字符集的字符流
InputStreamReader(InputStream in, String charsetName)
: 创建一个指定字符集的字符流
ObjectOutputStream序列化流
将Java对象的原始数据类型写出到文件,实现对象的持久存储。
public ObjectOutputStream(OutputStream out)
: 创建一个指定OutputStream的ObjectOutputStream
序列化:用一个字节序列可以表示一个对象,该字节序列包含该对象的数据
、 对象的类型
和对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。 对象的数据
、 对象的类型
和 对象中存储的数据
信息,都可以用来在内存中创建对象。
一个对象要想序列化,必须满足两个条件:
- 该类必须实现
java.io.Serializable
接口, Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
- 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用
transient
关键字修饰
对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException
异常
当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操 作也会失败,抛出一个 InvalidClassException
异常。发生这个异常的原因如下:
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
Serializable
接口给需要序列化的类,提供了一个序列版本号。serialVersionUID
该版本号的目的在于验证序列化的对象和对应类是否版本匹配
PrintStream打印流
网络编程
TCP
传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前, 在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可 靠。
- 第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
- 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
- 第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示:
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。
UDP
用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个 数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中,例如视频会议、QQ聊天等。
IP地址
指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设 备做唯一的编号。
端口号
用于标识进程
利用 协议
+ IP地址
+ 端口号
三元组合,就可以标识网络中的进程了,进程间的通信可以利用这个标识与其它进程进行交互。
简单的TCP通信分析
- 【服务端】启动,创建ServerSocket对象,等待连接。
- 【客户端】启动,创建Socket对象,请求连接。
- 【服务端】接收连接,调用accept方法,并返回一个Socket对象。
- 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
- 【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。
到此,客户端向服务端发送数据成功。
自此,服务端向客户端回写数据。
- 【服务端】Socket对象,获取OutputStream,向客户端回写数据。
- 【客户端】Scoket对象,获取InputStream,解析回写数据。
- 【客户端】释放资源,断开连接。
函数式接口
有且仅有一个抽象方法的接口
@FunctionalInterface
:一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
Lambda的延迟执行
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以 作为解决方案,提升性能。
Supplier接口
仅包含一个无参的方法: T get()
。用来获取一个泛型参数指定类型的对象数据
Consumer接口
与Supplier
接口相反,不是生产一个数据,而是使用一个数据, 其数据类型由泛型决定
包含抽象方法 void accept(T t)
,意为使用一个指定泛型的数据
Predicate接口
用于对某种类型的数据进行判断,从而得到一个boolean值结果
Function接口
用来根据一个类型的数据得到另一个类型的数据
中主要的抽象方法为: R apply(T t)
,根据类型T的参数获取类型R的结果
Stream流
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.filter(n -> n % 2 == 1)//奇数
.map(n -> n * n)//平方
.reduce(0, Integer::sum);//累加
System.out.println(sum);
}
结果为:35
Junit
用于单元测试
反射:框架设计的灵魂
将类的各个组成部分封装为其他对象,这就是反射机制
好处:
1. 可以在程序运行过程中,操作这些对象
2. 可以解耦,提高程序的可扩展性
注解
- 定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
- 概念描述:
- JDK1.5之后的新特性
- 说明程序的
- 使用注解:@注解名称
- 概念描述: