day03 List Set集合
常见数据结构: 堆栈, 队列, 数组, 链表, 红黑树
栈
先进后出(FILO)(first int last out)
栈的入口、出口的都是栈的顶端位置。
压栈:存元素。
弹栈:取元素。
队列 先进先出
数组
查询快 增删慢
数据结构
链表 :linked list, { ArrayList底层是一个数组}
查询慢 (没索引) 增删快
链表的适用场景:
查询少, 增删多的场景
链表可以实现栈和队列的结构, 因为栈和队列增删频繁
由结点组成.结点: 两部分 1. 数据域 2. 指针域
红黑树
二叉树:每个结点不超过2的有序树
二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。
查找: 节点存储的元素是按照大小顺序存储
特点:
元素存储过程中就完成了大小排序
查询比链表快, 增删比数组快 (数组和链表的折中)
红黑树的适用场景:
查询和增删都有, 需要元素自动排序的场景
红黑树的约束:
1. 节点可以是红色的或者黑色的
2. 根节点是黑色的
3. 叶子节点(特指空节点)是黑色的
4. 每个红色节点的子节点都是黑色的
5. 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
数据结构特点
List集合
1 有序 2 元素可重复 3 有索引
常用方法: 增删改查
public void add(int index, E element)
public E remove(int index)
public E set(int index, E element)
public E get(int index)
ArrayList集合
数组结构。元素增删慢,查找快
应用场景: 查询数据、遍历数据.
LinkedList集合
双向链表结构。方便元素添加、删除的集合。查询慢.
HashSet
根据对象的哈希值确定元素在集合中存储位置
因此具有良好的存取和查找性能
数据结构 数组+链表+红黑树 8
当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
哈希值主要是为了提高对象存储在 哈希表 中的效率
去重 重写 hashCode 与equals 方法。
HashSet特点:
1. 元素不可重复
2. 没有索引
3. 元素存取无序 (存入和取出顺序有可能不一致)
4. 底层采用 哈希表 结构. (查询快)
哈希表 = 数组 + 链表或红黑树
LinkedHashSet集合
LinkedHashSet特点:
1. 元素存取有序 (存入和取出顺序一致)
2. 元素不可重复
3. 没有索引
总结: 什么时候用List, 什么时候用Set?
要存储的元素可以重复的, 用List集合:
增删少, 用ArrayList
增删多, 用LinkedList
要存储的数据要求不重复, 或者相对一个集合去重, 用Set集合:
不要求存取顺序一致, 用HashSet
要求存取顺序一致, 用LinkedHashSet
可变参数
可变参数的本质就是一个"数组"
注意事项:
1. 可变参数可以传递的参数个数, 可以是 0个, 1个, 多个
2. 一个方法的参数列表中, 只能有一个可变参数
3. 如果方法的参数有多个, 可变参数必须写在参数列表的最后
Collections集合工具类
静态方法:
boolean addAlladdAll(Collection<? super T> c, T... elements):往集合中添加一些元素
static void shuffle(List<?> list): 打乱集合顺序
sort(List<T> list): 将集合中元素按照默认规则排序
sort(List<T> list,Comparator<? super T> c):将集合中元素按照指定规则排序
Collections集合工具类: sort(List list)
sort(List<T> list): 默认按照"升序"将元素排序
数字, 字母, 都可以按照升序排序
自定义JavaBean对象要想排序重写 int compareTo(E e) 方法
规则:
this-参数: 升序(从小到大)
参数-this: 降序(从大到小)
Collections集合工具类: sort(List list,Comparator<? super T> )
Comparable接口和Comparator接口区别
Comparable: 让JavaBean自身具有可比较性 (自己和其他人比)
Comparator: 定义一个比较器类, 用比较器对象比 (让第三个人来帮两个人比较)
Comparator使用方式:
1. 定义类实现Comparator<E>接口, 重写 int compare(E o1, E o2) 方法, 泛型为比较元素的类型
规则:
o1-o2: 升序(从小到大)
o2-o1: 降序(从大到小)
2. 在Collections.sort(List<T> list,Comparator<? super T> c)方法中传入自定义比较器对象
day04 Map集合
Map集合
Map接口与Collection接口没有关系,因此Map集合不能直接用foreach遍历,需要返回Set集合内才能遍历
双列集合,键不重复
常用子类
HashMap:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需
要重写键的hashCode()方法、equals()方法。
LinkedHashMap:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
Map集合键找值方式
一种是获取所有键组成Set集合
一种是获取键值对Entry组成Set集合
1.Set< E > 变量名 = Map集合名.keyset();
所有键存储到Set集合,遍历键的Set集合,根据键找值.get(K key)
2. public K getKey() :获取Entry对象中的键。
public V getValue() :获取Entry对象中的值。
在Map集合中获取所有Entry对象的方法:
public Set<Map.Entry<K,V>> entrySet() : 获取到Map集合中所有的键值对对象的集合(Set集合)。
Set<Map.Entry<V,E>> 变量名 = Map集合.entrySet();
1:of()方法只是Map,List,Set这三个接口的静态方法,其父类接口和子类实现并没有这类方法,比如HashSet,ArrayList等
2:返回的集合是不可变的;
day05 异常 多线程
Throwable 是异常体系最顶层
每个异常有对应的"类"描述,发生异常就会出现这个异常的"对象",异常不是语法错误.
异常分为 Error(不能处理的错误) 和 Exceotion(可以处理的异常)
错误(Error): 不能捕获处理的严重问题. 绝症
必须将程序停下来, 修改代码才能解决
错误的类名都是 "XxxError" 方式
1. 编译时异常:
编译时期就会发生的异常, 必须在编译时期处理
2. 运行时异常:
编译时正常, 运行时才会发生的异常
异常的过程
代码块发生异常,JVM创建一个对应的异常对象(包括内容,原因,位置). 如果内部没有 try...catch 处理,一直到main方法,依然没有,则JVM按默认方式处理.
JVM处理方式: 1. 异常信息打印 2. 终止当前程序
异常关键字: throw制造异常
throw关键字作用:
在方法中抛出指定的异常对象
throw 有结束作用,throw发生作用后,下方的代码不执行
Objects工具类方法: requireNonNull()非空判断
静态方法
static <T> T requireNonNull(T obj): 检查传入参数是不是null. 是null则抛异常; 非null则返回该对象
static <T> T requireNonNull(T obj, String message): 检查传入参数是不是null. 是null则抛异常, 同时带有原因; 非null则返回该对象
异常的处理方式
1. throws声明抛出异常
2. 捕获异常
注意:
如果 try 中产生了异常, 就会从产生异常的那一行代码直接跳转到对应的 catch 中执行处理代码, 然后
继续执行 try...catch 之后的其他代码; 如果 try 中没有产生异常, 那就不会执行 catch , 执行完 try 中
的代码后, 继续执行 try...catch 之后的其他代码
捕获多个异常的3种方式
1. 多个异常分别 try...catch 处理
2. 一个 try 多个 catch
3. 多个异常, 一次捕获一次处理
Throwable中的3个异常处理方法
String getMessage(): 异常的信息. 没有原因返回null
String toString(): 异常的类型和原因信息
void printStackTrace(): 使用标准错误输出流打印异常信息
finally代码块
无论是否发生异常, 是否捕获, 最后都会执行finally内部代码.
注意:
1. finally 必须和 try...catch 一起使用
2. finally 一般用于释放资源 (IO流时用到)
如果 finally 代码块中有 return 语句, 则永远返回 finally 中的 return 语句的值
应该避免在 finally 中写 return 语句
子父类继承重写方法时的异常要求
子父类继承关系下, 子类重写父类带有 throws 的方法:
1. 如果父类抛出多个异常, 子类重写父类方法时可以有3种方式:
a: 抛出和父类相同的异常
b: 抛出父类异常的子类
c: 不抛出异常
2. 父类方法没有抛出异常, 子类重写父类该方法时也不可抛出异常
此时子类产生该异常, 只能捕获处理, 不能声明抛出
一般情况下:
父类方法声明的异常是什么样的, 子类重写的方法声明异常就什么样, 保持一致即可
多线程
计算机基本概念: 并发与并行
并发: (交替执行) 指两个或多个事件在"同一时间段内"发生
并行: (同时执行) 指两个或多个事件在"同一时刻"发生 (同时发生)
我们研究的是"并发"
并发的实现, 是依靠电脑CPU快速地在多个任务之间切换执行实现的
进程
进程: 一个应用程序在内存中的一次执行过程
每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程
进程也是程序的一次执行过程,是系统运行程序的基本单位
系统运行一个程序即是一个进程从创建、运行到消亡的过程
线程
线程: 是进程内的一个独立执行单元 (一条代码执行路径)
一个程序运行后至少有一个进程, 一个进程中可以包含多个线程
多线程的好处:
效率高
多个线程之间互不影响
线程的调度
方式:
1. 分时调度: 所有线程轮流使用CPU, 平分占用CPU的时间
2. 抢占式调度: 优先让优先级高的线程使用CPU; 如果优先级相同, 则随机选择一个线程执行
Java使用的是"抢占式"调度
主线程
我们以前编写的代码, 也在一条线程中执行, 该线程叫作"main线程", 也称为"主线程"
如果我们没有额外创建线程, 那么我们的程序就只有一个线程, 即主线程, 此时程序是"单线程"的
单线程的执行特点:
同一个线程内的代码, 从上往下依次执行
day06 线程 同步 线程间通信
线程
多线程原理1: 线程执行的随机性
CPU高速随机切换 (本质)
线程抢夺CPU资源
多线程原理2: 多线程的内存
多线程情况下, 每个线程都有各自的栈内存
每个线程各自的方法调用, 进的是各自线程的栈
"栈"是每个线程各自的, "堆"是所有线程共用的
Thread常用方法:
java.lang.Thread类: 表示线程. 实现了Runnable接口
构造方法
Thread(): 创建Thead对象
Thread(String threadName): 创建Thead对象并指定线程名
Thread(Runnable target): 通过Runnable对象创建Thread对象
Thread(Runnable target, String threadName): 通过Runnable对象创建对象并指定线程名
成员方法
void run(): 用于让子类重写, 表示该线程要执行的任务.不能直接调用
void start(): 启动线程, 即让线程开始执行run()方法中的代码
String getName(): 获取线程的名称
void setName(String name): 设置线程名称
// 静态方法
static Thread currentThread(): 返回对当前正在执行的线程对象的引用
static void sleep(long millis): 让所在线程睡眠指定的毫秒
创建多线程程序的两种方式
一. 继承Thread类
1. 定义类继承Thread
2. 重写run()方法, 要执行的任务
3. 创建子类的对象, 调用start()方法启动线程
二. 实现Runnable接口
1. 定义类, 实现Runnable接口
2. 重写 run() 方法, 要执行的代码(任务)
3. 创建Runnable实现类对象 (任务对象)
4. 创建Thread类对象, 在构造方法中传入Runnable实现类对象 (将任务和线程绑定)
5. 通过Thread对象调用 start() 方法启动线程
java.lang.Thread类: 表示线程. 实现了Runnable接口
// 构造方法
Thread Thread(Runnable target): 通过Runnable对象创建Thread对象
Thread Thread(Runnable target, String threadName): 通过Runnable对象创建对象并指定线程名
Thread和Runnable的区别
实现Runnable的好处:
1. 避免java单继承的局限性
2. 增强了程序的扩展性, 降低了程序的耦合性(增强线程与任务之间的独立性,追求 "低耦合")
线程是Thread, 任务是Runnable实现类对象. 相当于将线程和任务分离
匿名内部类方式创建线程
1. new Runnable( 重写run方法 ) , 用一个变量接收.再用线程对象启动 run方法 的任务,创建线程.
1. new Thread( new Runnable 重写run方法 ).start;
2. new Thread( ) { 重写 run 方法 }.start.
线程安全
原因
问题发生场景:
多个线程操作共享资源
问题发生原因:
JVM是抢占式调度, CPU在每个线程之间切换是随机的, 代码执行到什么位置是不确定的
在操作共享资源时, 由于一个线程还没有执行完, 另一个线程就来操作, 就会出现问题
如何解决:
在操作共享资源时, 让线程一个一个来执行, 不要并发操作共享变量, 就可以解决问题
解决线程安全问题方式
1. 同步代码块
使用 synchronized 关键字修饰的代码块, 并传入一个当作锁的对象
格式:
synchronized (锁对象) {
// 操作共享数据的代码
}
注意:
锁对象可以是"任意类型的一个对象"
锁对象必须是"被多个线程共享的唯一的"对象
锁对象的作用: 只让一个线程在同步代码块中执行
2. 同步方法
同步方法: 使用 synchronized 关键字修饰的方法, 具有默认的锁对象
非静态同步方法的锁对象: this
非静态同步方法
public synchronized void method(){
// 可能会产生线程安全问题的代码
}
静态同步方法
public static synchronized void method(){
// 可能会产生线程安全问题的代码
}
静态同步方法的锁对象: 当前类的字节码对象 Class对象
RunnableImpl.class -> Class对象
获取一个类的字节码对象的3种方式:
1. 对象名.getClass() new RunnableImpl().getClass()
2. 类名.class RunnableImpl.class
3. Class.forName("类的全名"); Class.forName("com.itheima.test05.RunnableImpl");
字节码对象的特点:
同一个类, 他的字节码对象只有"唯一的一个"
3. Lock锁机制
Lock接口
成员方法
void lock(): 获取锁
void unlock(): 释放锁
线程间的通信
线程状态概述
锁对象, 也称为"同步锁", "对象锁", "对象监视器"
Object类中关于线程的方法:
java.lang.Object类:
// 成员方法 (<<注意>>: 只能通过"锁对象"调用)
void notify(): 随机唤醒在同一个锁对象上的某一个处于等待状态的线程
void notifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程
void wait(): 让当前线程处于"无限等待"状态
void wait(long timeout): 让当前线程处于"计时等待"状态, 时间到或被唤醒后结束此状态
void wait(long timeout, int nanos): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态
"注意!! 以上方法只能通过锁对象调用"
线程的生命周期中, 可以出现有6种状态:
1. "NEW 新建"
线程被创建, 但没有调用 start() 启动
2. "RUNNABLE 可运行"
调用 start()方法后已启动, 但可能正在执行 run() 方法的代码, 也可能正在等待CPU的调度
3. "BLOCKED (锁)阻塞"
线程试图获取锁, 但此时锁被其他线程持有
4. "WAITING 无限等待"
通过锁对象调用无参的 wait() 进入此状态.
等待其他线程通过锁对象执行 notify() 或 notifyAll() 才能结束这个状态
5. "TIMED_WAITING 计时等待"
如通过锁对象调用有参的 wait(long millis) 或 sleep(long millis), 则进入此状态.
直到时间结束之前被其他线程通过锁对象执行 notify()或 notifyAll()唤醒, 或时间结束自动唤醒
6. "TERMINATED 终止"
run()方法结束(执行结束, 或内部出现异常), 则进入此状态
Object类中wait(long timeout)和notifyAll()方法
java.lang.Object类:
// 成员方法 (只能通过"锁对象"调用)
void notifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程
void wait(long timeout): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态
wait() 和 sleep() 的区别:
1. wait会释放锁, 恢复时需要重新获取锁; sleep不会释放锁
2. wait可以被notify/notifyAll唤醒; sleep不会
3. wait要用锁对象调用; sleep要用Thread类名调用
|
|