黑马程序员技术交流社区
标题:
[石家庄校区]第二阶段day03-day06
[打印本页]
作者:
GIAO
时间:
2018-11-20 15:35
标题:
[石家庄校区]第二阶段day03-day06
第二阶段day03-day06
day03
1.
常见的数据结构
数据存储的常用结构有:栈、队列、数组、链表和红黑树。
栈
:
stack
,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。
先进后出,入口、出口的都是栈的顶端位置。
队列:
queue
,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。
先进先出,队列的入口、出口各占一侧(类似火车过隧道,头先从入口进,头也先从出口出)
数组
:
Array
,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。查询快,增删慢.
查找元素快:通过索引,可以快速访问指定位置的元素,
指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。
链表:
linked list
,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表,那么这里给大家介绍的是
单向链表
。
[attach]259543[/attach][attach]259542[/attach]
查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
增删元素快:增加元素:只需要修改连接下个元素的地址即可。
红黑树
二叉树:binary tree ,是每个结点不超过2的有序树(tree) 。
红黑树的特点:
速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍
[attach]259545[/attach]
2.
List接口
java.util.List接口继承自Collection接口,是单列集合的一个重要分支,习惯性地会将实现了List接口的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。
List接口特点:
1. 元素存取有序 (存入和取出元素的顺序一致) 321->321 排序: 从小到大
2. 元素可以重复 1 1 1 1
3. 有索引,
与数组的索引是一个道理.
List子体系中的实现类都具有上述特点
java.util.List接口:
// 常用特有成员方法 (都是按照索引来操作的)
void add(int index, E element): 将指定的元素, 添加到该集合中的指定位置上
E get(int index): 返回集合中指定位置的元素
E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素
E set(int index, E element): 用指定元素替换集合中指定位置的元素, 返回值的更新前的元素
list.add("嗯嗯"): 往 尾部添加 指定元素
list.add(1,"没头脑")往指定位置添加
list.remove(2)删除索引位置为2的元素
list.set(0, "三毛")在指定位置 进行 元素替代(改)
跟size() 方法一起用 来 遍历的
for(int i = 0;i<list.size();i++){
System.out.println(list.get(i));
2.1List的子类
ArrayList集合
ArrayList集合:
ArrayList底层的数据结构: 数组
ArrayList的特点:查询快,增删慢,线程不安全, 效率高
ArrayList适用场景:存储的数据"查询多, 增删少"的场景. 如用一个ArrayList存储中国城市名称
LinkedList集合
LinkedList底层的数据结构: 链表
LinkedList的特点:查询慢,增删快,线程不安全, 效率高
LinkedList适用场景:存储的数据"查询少, 增删多"的场景. 如用LinkedList实现栈或队列
java.util.LinkedList<E>类: 链表结构, 查询慢, 增删快
// 特有成员方法(主要操作开头和末尾元素)
void addFirst(E e): 将指定元素插入此列表的开头
void addLast(E e): 将指定元素添加到此列表的结尾
E getFirst(): 返回此列表的第一个元素
E getLast(): 返回此列表的最后一个元素
E removeFirst(): 移除并返回此列表的第一个元素
E removeLast(): 移除并返回此列表的最后一个元素
E pop(): (其实就是removeFirst())从此列表所表示的栈中弹出一个元素
void push(E e): (其实就是addFirst())将元素添加到此列表所表示的栈中
[attach]259546[/attach]
2.2Set接口:Set集合体系特点:1. 元素不可重复2. 没有索引
HashSet特点:
1. 元素不可重复
2. 没有索引
3. 元素存取无序 (存入和取出顺序有可能不一致)
4. 底层采用 哈希表 结构. (查询快)
哈希表 = 数组 + 链表或红黑树
java.util.HashSet类:
// 常用方法
boolean add(E e): 添加元素, 根据元素的 hashCode() 和 equals() 方法判断是否重复. 重复则不添加并返回false, 不重复则添加并返回true
6.png
(166.72 KB, 下载次数: 7)
下载附件
2018-11-20 15:29 上传
LinkedHashSet集合
LinkedHashSet特点:
1. 元素存取有序 (存入和取出顺序一致)
2. 元素不可重复
3. 没有索引
LinkedHashSet底层数据结构:
哈希表 + 链表 (也就是: 数组 + 链表或红黑树 + 链表)
其中, 哈希表用于存储数据, 额外的链表用于记录元素添加时的先后顺序, 以便在获取元素时保持顺序一致
总结: 什么时候用List, 什么时候用Set?
要存储的元素可以重复的, 用List集合:
增删少, 用ArrayList
增删多, 用LinkedList
要存储的数据要求不重复, 或者相对一个集合去重, 用Set集合:
不要求存取顺序一致, 用HashSet
要求存取顺序一致, 用LinkedHashSet
Collection接口: 单列集合的根接口, 规定了公共的功能
|
|_ List接口: 元素存取有序, 可重复, 有索引
| |_ Vector类: 底层数组, 有索引, 内存空间连续分配, 查询快, 增删慢, 线程安全, 效率低
| |_ ArrayList类: 底层数组, 有索引, 内存空间连续分配, 查询快, 增删慢, 线程不安全, 效率高
| |_ LinkedList类: 底层链表, 查询慢, 增删快
| |_ 遍历
| - toArray(): 可以
| - 普通for: 可以
| - 增强for: 可以
| - 迭代器: 可以
|
|_ Set接口: 元素不可重复, 无索引
|_ HashSet类: 底层哈希表, 元素无序, 元素不可重复(用hashCode()和equals()方法来判断)
| |_ LinkedHashSet类: 哈希表+链表, 同时具有HashSet的元素不重复, 链表存取有序的特点
|
|_ TreeSet类: 底层红黑树结构(存入元素时就会按照元素自然顺序排序).
|
|_ 遍历
- toArray(): 可以
- 普通for: 不可以, 没有索引, 不能用!
- 增强for: 可以
- 迭代器: 可以
day04
java.util.Map接口: 双列集合的顶层
// 成员方法
V put(K key, V value): 添加/修改 键值对.
如果键存在, 则用新值替换已有值, 返回被替换的值; 如果键不存在, 添加键值对, 返回null
V remove(Object key): 根据键删除键值对, 返回被删除元素的值
如果键不存在, 返回null
V get(Object key): 根据键获取值.
如果键不存在, 则返回null
boolean containsKey(Object key): 判断是否包含指定的键
Set<K> keySet(): 获取Map集合中所有的键, 存储到Set集合中
Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的Entry对象的集合(Set集合)
Map遍历方式一: keySet()方法实现通过键找值
Hashmap<String,Integer>map=new HashMap<>();
map.put( , );
map.put( ,);
map.put( , );
Set<String>keyset=map.keyset();// 通过keySet()先获取包含所有key的set集合
遍历set集合, 获取每个key
for (String key : keySet) {
// 通过key获取value
Integer value = map.get(key);
// 打印键值对
System.out.println(key + "=" + value);
}
简化的增强for循环
for (String key : map.keySet()) {
// 通过key获取value
Integer value = map.get(key);
// 打印键值对
System.out.println(key + "=" + value);
}
迭代器
Iterator<String> iterator = keySet.iterator();
while (iterator.hasNext()) {
String key = iterator.next();
Integer value = map.get(key);
System.out.println(key + "=" + value);
}
Entry
java.util.Map.Entry接口:
// 常用成员方法
K getKey(): 获取Entry对象中的键
V getValue(): 获取Entry对象中的值
Entry对象就是一个节点, 节点中存储了key和value
拿到一个Entry对象就可以从中获取key和value
Map遍历方式2: 通过entrySet()获取Entry对象形式遍历
Map<String, String> map = new HashMap<>();
// keySet()遍历
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
// 通过Entry对象获取每个键值对
String key = entry.getKey();
String value = entry.getValue();
// 打印当前键值对
System.out.println(key + "=" + value);
}
HashMap存储自定义数据类型作为键
HashMap存储自定义JavaBean对象作为key保证key唯一不重复, 需要让JavaBean重写 hashCode() 和 equals() 方法
LinkedHashMap类
LinkedHashMap底层: 哈希表 + 链表
key不允许重复, 但key存取有序
Hashtable类
Hashtable和HashMap:
相同点:
1. 底层都是哈希表
不同点:
1. Hashtable不允许存储 null 值和 null 键; HashMap允许存储 null 值和 null 键
2. Hashtable线程安全效率低; HashMap线程不安全效率高
Hashtable目前很少使用.
但其子类 Properties 集合, 可以与IO流结合使用, 应用较多
JDK9对集合添加的优化(of)
1. of() 方法只适用于List接口, Set接口, Map接口, 不适用于接口的实现类
2. of() 方法的返回值是一个不可变的集合, 集合不能再使用 add(), put() 方法添加元素, 会抛出异常
3. Set接口和Map接口在调用 of() 方法的时候, 不能有重复的元素, 否则会抛出异常
将 不可变集合 的元素转移到常见集合实现类中:
// List
ArrayList<Integer> list = new ArrayList<>(List.of(1,2,3));
// Set
HashSet<Integer> set = new HashSet<>(Set.of(1,2,3));
// Map
HashMap<Integer, String> map = new HashMap<>(Map.of(1,"a",2,"b"))
断点:
breakpoint, 在debug模式下, 程序运行到断点会暂停住, 便于我们在程序运行过程中查看
Debug调试程序:
可以让代码逐行执行,查看代码执行的过程,调试程序中出现的bug
使用方式:
在行号的右边,鼠标左键单击,添加断点(每个方法的第一行,哪里有bug添加到哪里)
右键,选择Debug执行程序
程序就会停留在添加的第一个断点处
执行程序:
f8:逐行执行程序
f7:进入到方法中
shift+f8:跳出方法
f9:跳到下一个断点,如果没有下一个断点,那么就结束程序
ctrl+f2:退出debug模式,停止程序
Console:切换到控制台
day05
异常的体系结构
java.lang.Throwable // 体系最顶层
|_ Error // 错误
|_ Exception // 编译时异常
|_ RuntimeException // 运行时异常
异常产生的过程:
1. 当执行的代码发生异常操作时, JVM会创建一个对应的异常类对象, 包含异常的内容, 原因, 位置
如 new ArrayIndexOutOfBoundsException(), new NullPointerException()
2. 如果执行代码的方法没有对异常进行 try...catch 处理, 则会向该方法调用处的方法抛(向上层抛). 如果所有方法(包括main()方法)都没有 try...catch 处理异常, 则该异常会被JVM按照默认的处理方式处理
3. JVM对于异常的默认处理方式是:
1. 将异常信息(内容, 原因, 位置)打印到控制台
2. 终止当前的程序
throw制造异常
格式:throw new 异常类名("异常原因字符串");
1. throw 必须写在方法的内部
2. throw 后面new的异常对象, 必须是 "Exception" 或 "Excetion的子类" 的对象
3. 一个方法内部 throw 了一个异常对象, 则该方法可以分为2种情况来处理该异常:
如果 throw 的是"运行时异常"(RuntimeException及其子类)对象, 那么可以不处理
该异常最终会交给JVM处理, 结果就是: 打印异常信息到控制台, 并立刻结束程序
如果 throw 的是"编译时异常"(Exception及其子类), 则必须处理:
处理方式1: throws 抛出
处理方式2: try...catch 捕获
异常的处理方式1: throws声明抛出异常
throws, 声明抛出 (方法自己不处理, 交给方法调用者处理, "甩锅给别人")
作用: 告诉方法的调用者, 调用此方法有可能发生异常, 需要在编写代码时处理
格式:
修饰符 返回值类型 方法名() throws 异常类名1, 异常类名2, ... {
内部抛出异常
}
1. throws 必须写在方法声明上
2. throws 后面的异常类名, 一般是 Exception 或 Exception的子类
(RuntimeException及其子类也行, 但是没有什么意义)
3. 方法内部如果抛出了多个异常对象, throws 后也必须声明多个异常
如果抛出的异常对象有子父类关系, 那么直接声明父类异常即可
4. 调用了一个带有 throws 声明抛出异常的方法, 就必须处理该异常:
要么继续声明 throws 抛出
要么 try...catch 捕获处理异常
异常处理方式2: 捕获异常
try {
// 可能产生异常的代码
} catch (异常类名 变量名) {
// 处理异常的代码
// 一般会将异常信息存储到日志中
}
...
} catch (异常类名 变量名) {
// 处理异常的代码
// 一般会将异常信息存储到日志中
}
注意:
1. try 中可能会抛出多种异常, 就可以写多个 catch 分别处理每种异常
2. 如果 try 中产生了异常, 就会从产生异常的那一行代码直接跳转到对应的 catch 中执行处理代码, 然后继续执行 try...catch 之后的其他代码; 如果 try 中没有产生异常, 那就不会执行 catch , 执行完 try 中的代码后, 继续执行 try...catch 之后的其他代码
finally代码块
try {
// 可能发生异常的代码
} catch(异常类型 异常变量名) {
// 处理异常
}
...
catch(异常类型 异常变量名) {
// 处理异常
} finally {
// 无论是否发生异常, 是否捕获, 最后都会执行的代码.
// 通常在这里执行释放资源的操作
}
1. finally 必须和 try...catch 一起使用
2. finally 一般用于释放资源 (IO流时用到)
finally中有return语句,:果 finally 代码块中有 return 语句, 则永远返回 finally 中的 return 语句的值
应该避免在 finally 中写 return 语句
常注意事项3: 子父类继承重写方法时的异常要求
子父类继承关系下, 子类重写父类带有 throws 的方法:
1. 如果父类抛出多个异常, 子类重写父类方法时可以有3种方式:
a: 抛出和父类相同的异常
b: 抛出父类异常的子类
c: 不抛出异常
2. 父类方法没有抛出异常, 子类重写父类该方法时也不可抛出异常
此时子类产生该异常, 只能捕获处理, 不能声明抛出
一般情况下:
父类方法声明的异常是什么样的, 子类重写的方法声明异常就什么样, 保持一致即可
// 父类方法声明抛出异常, 子类重写方法也抛出
public class Fu {
public void method() throws IOException {
// ...
}
}
public class Zi extends Fu {
@Override
public void method() throws IOException { // 正确, 和父类一样
public void method() throws FileNotFoundException { // 正确, 是子类异常
public void method() throws { // 正确, 不抛出异常
// ...
}
}
// 父类没有异常, 子类重写方法也不能抛出
public class Fu {
public void method() {
// ...
}
}
public class Zi extends Fu {
@Override
public void method() {
// ...
}
}
并发与并行
并发:
(交替执行) 指两个或多个事件在"同一时间段内"发生,
依靠电脑CPU快速地在多个任务之间切换执行实现的
并行:
(同时执行) 指两个或多个事件在"同一时刻"发生 (同时发生)
程序 > 进程 > 线程
day06
Thread
类中的
start
()
方法与
run
()
方法的区别:
线程对象调用
run
()
方法不开启线程,仅是对象调用方法。线程对象调用
start
()
方法开启线程,并让
jvm
调用
run
()
方法在开启的线程中执行。
请描述创建线程的两种方式。
l
第一种方式是将类声明为
Thread
的子类。
1.
定义
Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
2.
创建
Thread子类的实例,即创建了线程对象。
3.
调用线程对象的
start()方法来启动该线程。
l
第二种方式是声明一个类实现
Runnable
接口。
1.
定义
Runnable
接口的实现类,并重写该接口的
run()
方法,该
run()
方法的方法体同样是该线程的线程执行体。
2.
创建
Runnable
实现类的实例,并以此实例作为
Thread
的
target
来创建
Thread
对象,
Thread
对象才是真正的线程对象。
3.
调用线程对象的
start()
方法来启动线程。
请描述实现
Runnable
接口比继承
Thread
类所具有的优势:
1.
适合多个相同的程序代码的线程去共享同一个资源。
2.
可以避免
java
中的单继承的局限性。
3.
增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数据独立。
4.
线程池只能放入实现
Runable
或
callable
类线程,不能直接放入继承
Thread
的类。
线程安全出现的原因:多个线程操作共享的数据
同步技术:
C:/Users/Administrator/AppData/Local/YNote/data/qq43AEA454F9E8FC8D3CE99A4053144AD8/a1b1e3e75f3a4aa5a23fa90c25a4544e/clipboard.png
synchronized (锁对象) {
// 操作共享数据的代码
}
锁对象:
类型: 任意
必须是多个线程共享的唯一一个对象
锁对象, 也称为"同步锁", "对象锁", "对象监视器"
同步方法:
public synchronized void method(){
可能会产生线程安全问题的代码}
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
Lock锁:
public void lock():加同步锁。
public void unlock():释放同步锁。
new Reentrantlock()
同步的原理:
线程进入同步代码块前, 会"争夺锁对象", "只有一个线程"会抢到锁对象
进入同步代码块的线程, 会"持有锁对象", 并执行同步代码块中的代码
此时同步代码块外的线程, 处于"阻塞"状态, 只能等待
当同步代码块内的线程执行完代码块, 会离开同步代码块, 并"归还锁对象"给同步代码块
等在同步代码块外的其他线程就可以继续争夺锁对象
线程的生命周期中, 可以出现有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()方法结束(执行结束, 或内部出现异常), 则进入此状态
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/)
黑马程序员IT技术论坛 X3.2