[石家庄校区]就业班,List与Set,Thread,Map,线程
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): 用指定元素替换集合中指定位置的元素, 返回值的更新前的元素
ArrayList底层的数据结构:
数组
ArrayList的特点:
查询快
增删慢
线程不安全, 效率高
ArrayList适用场景:
存储的数据"查询多, 增删少"的场景. 如用一个ArrayList存储中国城市名称
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())将元素添加到此列表所表示的栈中
Set集合体系特点:
1. 元素不可重复
2. 没有索引
HashSet特点:
1. 元素不可重复
2. 没有索引
3. 元素存取无序 (存入和取出顺序有可能不一致)
4. 底层采用 哈希表 结构. (查询快)
哈希表 = 数组 + 链表或红黑树
.HashSet类:
boolean add(E e): 添加元素, 根据元素的 hashCode() 和 equals() 方法判断是否重复. 重复则不添加并返回false, 不重复则添加并返回true
HashSet集合保证添加元素不重复的原理:
调用 add(E e) 添加元素时, 先调用 hashCode() 获取哈希值, 和当前HashSet集合中的元素比较
如果哈希值不同, 则认为元素不重复, 添加, 并返回true
如果哈希值相同, 则可能是哈希冲突, 所以继续调用元素的 equals() 方法和所有哈希值相同的元素比较
如果 equals() 比较所有元素都没有相同的, 则认为元素不重复, 添加, 并返回true
如果 equals() 比较出有相同的元素, 则认为元素重复, 不添加, 并返回false
LinkedHashSet特点:
1. 元素存取有序 (存入和取出顺序一致)
2. 元素不可重复
3. 没有索引
LinkedHashSet底层数据结构:
哈希表 + 链表 (也就是: 数组 + 链表或红黑树 + 链表)
其中, 哈希表用于存储数据, 额外的链表用于记录元素添加时的先后顺序, 以便在获取元素时保持顺序一致
可变参数:
JDK 5 出现. 指同一个类型的参数, "个数可变"
可变参数的本质就是一个"数组"
格式: 用在方法的参数中
修饰符 返回值类型 方法名(int... 变量名) {
// 可以直接将 变量名 当作 数组名 使用
}
方法名();
注意事项:
1. 可变参数可以传递的参数个数, 可以是 0个, 1个, 多个
2. 一个方法的参数列表中, 只能有一个可变参数
3. 如果方法的参数有多个, 可变参数必须写在参数列表的最后
Collections类: 操作集合的工具类
// 静态方法
static <T> boolean addAll(Collection<? super T> c, T... elements):往集合中添加一些元素
static void shuffle(List<?> list): 打乱集合顺序
static <T> void sort(List<T> list): 将集合中元素按照默认规则排序
static <T> void sort(List<T> list,Comparator<? super T> c):将集合中元素按照指定规则排序
compar
sort(List<T> list): 默认按照"升序"将元素排序
数字, 字母, 都可以按照升序排序
自定义JavaBean对象默认不能排序, 因为不知道如何比较哪个对象大, 哪个对象小
自定义JavaBean对象要想排序, 需要实现 Comparable<E> 接口, 重写 int compareTo(E e) 方法
规则:
this-参数: 升序(从小到大)
参数-this: 降序(从大到小)
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
|_ 迭代器
Map集合特点:
1. 是双列集合, 一个元素包含两个值 (键key, 值value) 键值对
2. key和value的类型可以相同, 也可以不同
3. key不允许重复, value可以重复
4. key和value是一一对应的, 一个键只能对应一个值
Map集合适合存储"一对一关系"的数据
夫妻
String : Phone
统计次数: 某个字符出现了多少次 "aaababababba"
'a': 1
'b': 3
Map接口: 双列集合的顶层
// 成员方法
V?put(K?key,?V?value): 添加/修改 键值对.
如果键存在, 则用新值替换已有值, 返回被替换的值; 如果键不存在, 则添加键值对, 返回null
V?remove(Object?key): 根据键删除键值对, 返回被删除元素的值
如果键不存在, 返回null
V?get(Object?key): 根据键获取值.
如果键不存在, 则返回null
boolean containsKey(Object key): 判断是否包含指定的键
boolean containsKey(Object key): 判断是否包含指定的键
Set<K>?keySet(): 获取Map集合中所有的键, 存储到Set集合中
Set<Map.Entry<K,V>>?entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)
// JDK 9对于集合初始化的优化
java.util.List
// 静态方法
static List<E> of(E... e): 返回包含指定元素的 不可变List 集合
java.util.Set
// 静态方法
static Set<E> of(E... e): 返回包含指定元素的 不可变Set 集合
java.util.Map
// 静态方法
static Map<K, V> of(K k1, V v1): 返回包含指定键值对的 不可变Map 集合
keySet()遍历步骤:
1. Map对象调用 keySet() 方法, 获取包含所有key的Set集合
2. 遍历Set集合, 获取每个key
3. 通过Map对象调用 get(Object key) 方法根据key获取到value
Map<String, String> map = new HashMap<>();
// keySet()遍历
Set<String> keys = map.keySet();
for (String key : keys) {
// 通过每个键获取值
String value = map.get(key);
// 打印当前键值对
System.out.println(key + "=" + value);
}
java.util.Map接口
Set<Map.Entry<K,V>>?entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)
entrySet()方法遍历Map步骤:
1. Map对象调用 entrySet() 获取包含所有Entry对象的Set集合
2. 遍历Set集合, 获取每个Entry对象
3. 调用Entry对象的 getKey() 和 getValue() 方法获取键和值
Map<String, String> map = new HashMap<>();
// keySet()遍历
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
// 通过Entry对象获取每个键值对
String value = entry.getKey();
String value = entry.getValue();
// 打印当前键值对
System.out.println(key + "=" + value);
}
HashMap存储自定义数据类型作为键
HashMap存储自定义JavaBean对象作为key保证key唯一不重复, 需要让JavaBean重写 hashCode() 和 equals() 方法
LinkedHashMap类
LinkedHashMap底层: 哈希表 + 链表
key不允许重复, 但key存取有序
Debug调试程序:
breakpoint, 在debug模式下, 程序运行到断点会暂停住, 便于我们在程序运行过程中查看
可以让代码逐行执行,查看代码执行的过程,调试程序中出现的bug
使用方式:
在行号的右边,鼠标左键单击,添加断点(每个方法的第一行,哪里有bug添加到哪里)
右键,选择Debug执行程序
程序就会停留在添加的第一个断点处
执行程序:
f8:逐行执行程序
f7:进入到方法中
shift+f8:跳出方法
f9:跳到下一个断点,如果没有下一个断点,那么就结束程序
ctrl+f2:退出debug模式,停止程序
Console:切换到控制台
java.lang.Thread类: 表示线程. 实现了Runnable接口
// 构造方法
Thread Thread(): 创建Thead对象
Thread Thread(String threadName): 创建Thead对象并指定线程名
Thread Thread(Runnable target): 通过Runnable对象创建Thread对象
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): 让所在线程睡眠指定的毫秒
java.lang.Object类:
// 成员方法 (只能通过"锁对象"调用)
void notify(): 随机唤醒在同一个锁对象上的某一个处于等待状态的线程
void notifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程
void wait(): 让当前线程处于无限等待状态
void wait(long timeout): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态
void wait(long timeout, int nanos): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态
创建线程的第2种方式:
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对象创建对象并指定线程名
格式:
synchronized (锁对象) {
// 操作共享数据的代码
}
注意:
锁对象可以是"任意类型的一个对象"
锁对象必须是"被多个线程共享的唯一的"对象
锁对象的作用: 只让一个线程在同步代码块中执行
锁对象, 也称为"同步锁", "对象锁", "对象监视器"
同步的原理:
线程进入同步代码块前, 会"争夺锁对象", "只有一个线程"会抢到锁对象
进入同步代码块的线程, 会"持有锁对象", 并执行同步代码块中的代码
此时同步代码块外的线程, 处于"阻塞"状态, 只能等待
当同步代码块内的线程执行完代码块, 会离开同步代码块, 并"归还锁对象"给同步代码块
等在同步代码块外的其他线程就可以继续争夺锁对象
java.util.concurrent.locks.Lock接口: JDK 5 新增的Lock接口
// 成员方法
void lock(): 获取锁
void unlock(): 释放锁
java.util.concurrent.locks.ReentrantLock类: Lock的实现类
使用方式:
public class RunnableImpl implements Runnable {
// 成员变量创建锁对象, 该锁对象也要所有线程共享唯一一个
Lock lock = new ReentrantLock(); // 成员变量
@Override
public void run() {
// 加锁
lock.lock();
try {
// 操作共享变量的代码...
} finally {
// 在finally中保证释放锁
lock.unlock();
}
}
}
线程的生命周期中, 可以出现有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()方法结束(执行结束, 或内部出现异常), 则进入此状态
格式:
线程
try {
// 可能产生异常的代码
} catch (异常类名 变量名) {
// 处理异常的代码
// 一般会将异常信息存储到日志中
}
...
} catch (异常类名 变量名) {
// 处理异常的代码
// 一般会将异常信息存储到日志中
// JDK 7增加
try {
// 可能发生异常的代码
} catch (异常类型1 | 异常类型2 | 异常类型3 | ... 异常对象名) {
// 处理任意一个异常的代码
}
try {
// 可能发生异常的代码
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
// 处理异常的代码
}
java.lang.Throwable
// 成员方法
String getMessage(): 异常的信息. 没有原因返回null
String toString(): 异常的类型和原因信息
void printStackTrace(): 使用标准错误输出流打印异常信息
格式:
try {
// 可能发生异常的代码
} catch(异常类型 异常变量名) {
// 处理异常
}
...
catch(异常类型 异常变量名) {
// 处理异常
} finally {
// 无论是否发生异常, 是否捕获, 最后都会执行的代码.
// 通常在这里执行释放资源的操作
}
实现多线程的第一种方式:
1. 定义类, 继承 Thread 类
2. 重写 run() 方法, run方法内部是线程要执行的任务
3. 创建Thread子类的对象, 调用 start() 方法启动线程
|