day03-day06笔记整理
1、栈的特点:先进后出 (FILO, First In Last Out);入口和出口在同一侧
2、队列的特点:先进先出 (FIFO, First In First Out);入口和出口在两端
3、数组的特点:
查询快:通过 (第一个元素地址值 + 索引) 可以快速计算出该索引元素的地址值
增删慢:增加一个元素, 要创建长度+1的新数组, 然后将原数组元素复制到新数组, 然后存入新元素; 删除类似
4、链表的特点:
查询慢: 要找到其中某个节点, 只能从第一个节点一个一个向后寻找
增删快: 只需要修改保存的下一个节点的地址值, 就可以快速完成增删
5、红黑树的特点:
元素存储过程中就完成了大小排序
查询比链表快, 增删比数组快 (数组和链表的折中)
6、List集合的特点:
1.、元素存取有序 (存入和取出元素的顺序一致) 321->321 排序: 从小到大
2.、元素可以重复 1 1 1 1
3.、有索引
4、常用成员方法:void add(int index, E element)、E get(int index)、E remove(int index)、E set(int index, E element)
7、ArrayList的特点:
查询快
增删慢
线程不安全, 效率高
8、LinkedList的特点:
1、查询慢
2、增删快
3、线程不安全, 效率高
4、常用成员方法:void addFirst(E e)、void addLast(E e)、E getFirst()、E getLast()、E removeFirst()、E removeLast()、E pop()、void push(E e)
9、Vector的特点:
1、查询慢
2、增删快
3、(同步)线程安全, 效率低
9、HashSet特点:
1. 元素不可重复
2. 没有索引
3. 元素存取无序 (存入和取出顺序有可能不一致)
4. 底层采用 哈希表 结构. (查询快)
哈希表 = 数组 + 链表或红黑树
10、哈希表:
JDK 8以前 : 哈希表 = 数组 + 链表
JDK 8及之后 : 哈希表 = 数组 + 链表或红黑树
数组中存储的每个元素, 是哈希值相同的一组节点的链表或红黑树
11、HashSet集合保证添加元素不重复的原理:
调用 add(E e) 添加元素时, 先调用 hashCode() 获取哈希值, 和当前HashSet集合中的元素比较
如果哈希值不同, 则认为元素不重复, 添加, 并返回true
如果哈希值相同, 则可能是哈希冲突, 所以继续调用元素的 equals() 方法和所有哈希值相同的元素比较
如果 equals() 比较所有元素都没有相同的, 则认为元素不重复, 添加, 并返回true
如果 equals() 比较出有相同的元素, 则认为元素重复, 不添加, 并返回false
12、LinkedHashSet特点:
1. 元素存取有序 (存入和取出顺序一致)
2. 元素不可重复
3. 没有索引
LinkedHashSet底层数据结构:
哈希表 + 链表 (也就是: 数组 + 链表或红黑树 + 链表)
其中, 哈希表用于存储数据, 额外的链表用于记录元素添加时的先后顺序, 以便在获取元素时保持顺序一致
13、可变参数:
格式: 用在方法的参数中
修饰符 返回值类型 方法名(int... 变量名) {
// 可以直接将 变量名 当作 数组名 使用
}
方法名();
注意事项:
1. 可变参数可以传递的参数个数, 可以是 0个, 1个, 多个
2. 一个方法的参数列表中, 只能有一个可变参数
3. 如果方法的参数有多个, 可变参数必须写在参数列表的最后
14、Map集合特点:
1. 是双列集合, 一个元素包含两个值 (键key, 值value) 键值对
2. key和value的类型可以相同, 也可以不同
3. key不允许重复, value可以重复
4. key和value是一一对应的, 一个键只能对应一个值
15、Map中常用的方法:
V put(K key, V value)、V remove(Object key)、V get(Object key)、boolean containsKey(Object key)、Set<K> keySet()、Set<Map.Entry<K,V>> entrySet()
16、keySet()遍历步骤:
1. Map对象调用 keySet() 方法, 获取包含所有key的Set集合
2. 遍历Set集合, 获取每个key
3. 通过Map对象调用 get(Object key) 方法根据key获取到value
17、entrySet()方法遍历Map步骤:
1. Map对象调用 entrySet() 获取包含所有Entry对象的Set集合
2. 遍历Set集合, 获取每个Entry对象
3. 调用Entry对象的 getKey() 和 getValue() 方法获取键和值
18、Map双列集合总结:
Map接口: 双列集合的根接口, 规定了共性的方法
|_ HashMap类: 哈希表=数组+链表+红黑树. key无序不可重复, 可存null键null值, 线程不安全效率高
| |_ LinkedHashMap类: 哈希表+链表. 哈希表实现key不可重复, 链表实现key存取有序
|
|_ Hashtable类: 哈希表. Hash特性针对key, key无序不可重复, 不可存null键null值, 线程安全效率低
|_ TreeMap类: 红黑树结构(存入时就排序). Tree特性针对key, 可以按照key排序, 要求key具备比较性
|_ 遍历
|_ keySet(): 获取所有key组成的Set集合, 遍历Set集合拿到key, 通过Map的get(key)得到value
| |_ 对于Set<Key>的遍历
| |_ 增强for
| |_ 迭代器
|_ entrySet(): 获取所有的key和value组成的Entry对象的Set集合, 遍历出entry对象, 通过entry对象的getKey()获取对应的key, 通过Entry对象的getValue方法获取对应的value
|_ 对Set<Entry>的遍历
|_ toArray()
|_ 增强for
|_ 迭代器
19、异常与错误:
错误(Error): 不能捕获处理的严重问题. 绝症
必须将程序停下来, 修改代码才能解决
错误的类名都是 "XxxError" 方式
异常(Exception): 可以捕获处理的问题. 发烧感冒吃个药就好了
程序执行起来后, 如果有合适的处理方式, 即使发生异常, 程序也能处理该异常并继续运行
异常的类名都是 "XxxException" 方式
1. 编译时异常:
编译时期就会发生的异常, 必须在编译时期处理 Unhandled exception XxxException
2. 运行时异常:
编译时正常, 运行时才会发生的异常
20、异常产生的过程:
1. 当执行的代码发生异常操作时, JVM会创建一个对应的异常类对象, 包含异常的内容, 原因, 位置
如 new ArrayIndexOutOfBoundsException(), new NullPointerException()
2. 如果执行代码的方法没有对异常进行 try...catch 处理, 则会向该方法调用处的方法抛(向上层抛). 如果所有方法(包括main()方法)都没有 try...catch 处理异常, 则该异常会被JVM按照默认的处理方式处理
3. JVM对于异常的默认处理方式是:
1. 将异常信息(内容, 原因, 位置)打印到控制台
2. 终止当前的程序
21、throw制造异常:
异常产生的过程:
1. 当执行的代码发生异常操作时, JVM会创建一个对应的异常类对象, 包含异常的内容, 原因, 位置
如 new ArrayIndexOutOfBoundsException(), new NullPointerException()
2. 如果执行代码的方法没有对异常进行 try...catch 处理, 则会向该方法调用处的方法抛(向上层抛). 如果所有方法(包括main()方法)都没有 try...catch 处理异常, 则该异常会被JVM按照默认的处理方式处理
3. JVM对于异常的默认处理方式是:
1. 将异常信息(内容, 原因, 位置)打印到控制台
2. 终止当前的程序
22、throws声明抛出异常:
异常处理的第一种方式:
throws, 声明抛出 (方法自己不处理, 交给方法调用者处理, "甩锅给别人")
作用: 告诉方法的调用者, 调用此方法有可能发生异常, 需要在编写代码时处理
格式:
修饰符 返回值类型 方法名() throws 异常类名1, 异常类名2, ... {
}
注意:
1. throws 必须写在方法声明上
2. throws 后面的异常类名, 一般是 Exception 或 Exception的子类
(RuntimeException及其子类也行, 但是没有什么意义)
3. 方法内部如果抛出了多个异常对象, throws 后也必须声明多个异常
如果抛出的异常对象有子父类关系, 那么直接声明父类异常即可
4. 调用了一个带有 throws 声明抛出异常的方法, 就必须处理该异常:
要么继续声明 throws 抛出
要么 try...catch 捕获处理异常
23、try {} catch{} 捕获异常:
1. try 中可能会抛出多种异常, 就可以写多个 catch 分别处理每种异常
2. 如果 try 中产生了异常, 就会从产生异常的那一行代码直接跳转到对应的 catch 中执行处理代码, 然后继续执行 try...catch 之后的其他代码; 如果 try 中没有产生异常, 那就不会执行 catch , 执行完 try 中的代码后, 继续执行 try...catch 之后的其他代码
24、finally代码块:
1. finally 必须和 try...catch 一起使用
2. finally 一般用于释放资源 (IO流时用到)
25、捕获多个异常:
1. 多个异常分别 try...catch 处理
2. 一个 try 多个 catch
如果异常存在继承关系, 子类异常在上, 父类异常在下
3. 多个异常, 一次捕获一次处理
用Exception多态捕获
26、子父类继承关系下, 子类重写父类带有 throws 的方法:
1. 如果父类抛出多个异常, 子类重写父类方法时可以有3种方式:
a: 抛出和父类相同的异常
b: 抛出父类异常的子类
c: 不抛出异常
2. 父类方法没有抛出异常, 子类重写父类该方法时也不可抛出异常
此时子类产生该异常, 只能捕获处理, 不能声明抛出
一般情况下:
父类方法声明的异常是什么样的, 子类重写的方法声明异常就什么样, 保持一致即可
27、并发与并行:
并发: (交替执行) 指两个或多个事件在"同一时间段内"发生
并行: (同时执行) 指两个或多个事件在"同一时刻"发生 (同时发生)
28、线程:
线程: 是进程内的一个独立执行单元 (一条代码执行路径),一个程序运行后至少有一个进程, 一个进程中可以包含多个线程
多线程的好处:效率高
多个线程之间互不影响
29、实现多线程的第一种方式:
1. 定义类, 继承 Thread 类
2. 重写 run() 方法, run方法内部是线程要执行的任务
3. 创建Thread子类的对象, 调用 start() 方法启动线程
注意:
必须调用 start() 方法来开启线程, 不能直接调用 run() 方法, 调用 run() 会变成单线程
同一个线程对象, 不能多次调用 start() 方法
Java是抢占式调度, 不同线程的代码, 执行顺序是随机的
30、实现线程的一种方式:
继承Thread类
定义类继承Thread
重写run()方法, 要执行的任务
创建子类的对象, 调用start()方法启动线程
31、Thread常用方法:
Thread()、Thread(String threadName)、Thread(Runnable target)、Thread(Runnable target, String threadName)、 void run()、void start()、String getName()、void setName(String name)、static Thread currentThread()、static void sleep(long millis)
32、创建多线程的第二种方式:
1. 定义类, 实现Runnable接口
2. 重写 run() 方法, 要执行的代码(任务)
3. 创建Runnable实现类对象 (任务对象)
4. 创建Thread类对象, 在构造方法中传入Runnable实现类对象 (将任务和线程绑定)
5. 通过Thread对象调用 start() 方法启动线程
33、实现Runnable的好处:
1. 避免单继承的局限性
2. 增强了程序的扩展性, 降低了程序的耦合性(解耦)
线程是Thread, 任务是Runnable实现类对象. 相当于将线程和任务分离
耦合性: 相互之间的关系的紧密程度
耦合性高: 相互之间的关系非常紧密
耦合性低: 相互之间的关系不太紧密
我们追求 "低耦合"
34、线程安全问题:
问题发生场景:
多个线程操作共享资源
问题发生原因:
JVM是抢占式调度, CPU在每个线程之间切换是随机的, 代码执行到什么位置是不确定的
在操作共享资源时, 由于一个线程还没有执行完, 另一个线程就来操作, 就会出现问题
如何解决:
在操作共享资源时, 让线程一个一个来执行, 不要并发操作共享变量, 就可以解决问题
35、解决多线程操作共享数据的安全问题的3种方式:
1. 同步代码块
2. 同步方法
3. Lock锁机制
注意:
锁对象可以是"任意类型的一个对象"
锁对象必须是"被多个线程共享的唯一的"对象
锁对象的作用: 只让一个线程在同步代码块中执行
36、同步代码解决线程安全问题:
同步的原理:
线程进入同步代码块前, 会"争夺锁对象", "只有一个线程"会抢到锁对象
进入同步代码块的线程, 会"持有锁对象", 并执行同步代码块中的代码
此时同步代码块外的线程, 处于"阻塞"状态, 只能等待
当同步代码块内的线程执行完代码块, 会离开同步代码块, 并"归还锁对象"给同步代码块
等在同步代码块外的其他线程就可以继续争夺锁对象
37、同步方法:
使用 synchronized 关键字修饰的方法, 具有默认的锁对象
38、Lock锁解决线程安全问题:
实现方式:
public class RunnableImpl implements Runnable {
// 成员变量创建锁对象, 该锁对象也要所有线程共享唯一一个
Lock lock = new ReentrantLock(); // 成员变量
@Override
public void run() {
// 加锁
lock.lock();
try {
// 操作共享变量的代码...
} finally {
// 在finally中保证释放锁
lock.unlock();
}
}
}
39、线程生命周期的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()方法结束(执行结束, 或内部出现异常), 则进入此状态
40、wait() 和 sleep() 的区别:
1. wait会释放锁, 恢复时需要重新获取锁; sleep不会释放锁
2. wait可以被notify/notifyAll唤醒; sleep不会
3. wait要用锁对象调用; sleep要用Thread类名调用
|