一 Map集合(会)
import java.util.*;
/**
* @Author 千锋⼤数据教学团队
* @Company 千锋好程序员⼤数据
* @Description Map API
*/
public class MapUsage {
public static void main(String[] args) {
// 1. 实例化⼀个Map集合的实现类对象,并向上转型为接⼝类型。
Map<String, String> map = new HashMap<>();
// 2. 向集合中插⼊数据
String value = map.put("name", "xiaoming");
System.out.println(value); // 由于第⼀次添加这个键值
对,集合中没有被覆盖的值,因此返回null
String value2 = map.put("name", "xiaobai");
System.out.println(value2); // 这⾥是第⼆次设置name的
值,会⽤xiaobai覆盖掉xiaoming,因此返回xiaoming
// 3. 向集合中插⼊数据
String value3 = map.putIfAbsent("name", "xiaohong");
System.out.println(value3); // 这⾥返回的是集合中已经存
在的这个键对应的值
String value4 = map.putIfAbsent("age", "20");
System.out.println(value4); // 由于这个集合中原来是没有
age键存在的,所以返回的是null
// 4. 将⼀个Map集合中所有的键值对添加到当前的集合中
Map<String, String> tmp = new HashMap<>();
tmp.put("height", "177");
tmp.put("weight", "65");
tmp.put("age", "30");
map.putAll(tmp);
// 5. 删除:通过键,删除⼀个键值对,并返回这个被删除的键值对中的
值。
String value5 = map.remove("weight");
System.out.println(value5);
// 6. 删除
boolean value6 = map.remove("age", "30");
System.out.println(value6);
// 7. 清空集合
// map.clear();
// 8. 修改集合中的某⼀个键值对(通过键,修改值)
System.out.println(value7); // 返回被覆盖的值
String value8 = map.replace("age", "30");
System.out.println(value8); // 由于map中没有age键,因此这个返回null
// 9. 修改: 只有当key和oldValue是匹配的情况下,才会将值修改成
newValue。
boolean value9 = map.replace("name", "xiaohei",
"xiaohong");
System.out.println(value9);
// 10. 对集合中的元素进⾏批量的替换
// 将集合中的每⼀个键值对,带⼊到BiFunction的⽅法中,使⽤接
⼝⽅法的返回值替换集合中原来的值。
map.replaceAll((k, v) -> {
if (k.equals("height")) {
return v + "cm";
}
return v;
});
// 11. 通过键获取值。
String value10 = map.get("name1");
System.out.println(value10);
// 12. 通过键获取值,如果这个键不存在,则返回默认的值。
String value11 = map.getOrDefault("name1","aaa");
System.out.println(value11);
// 13. 判断是否包含某⼀个键
boolean value12 = map.containsKey("height");
System.out.println(value12);
boolean value13 = map.containsValue("177");
System.out.println(value13);
// 14. 获取由所有的键组成的Set集合
Set<String> keys = map.keySet();
// 获取由所有的值组成的Collection集合
Collection<String> values = map.values();
System.out.println(map);
}
}
1.1 Map集合的遍历(会)
.使⽤keySet进⾏遍历
1. 可以使⽤keySet()⽅法获取到集合中所有的键。
2. 遍历存储了所有的键的集合,依次通过键获取值。
/**
* 1. 使⽤keySet进⾏遍历
* @param map 需要遍历的集合
*/
private static void keyset(Map<String, String> map) {
// 1. 获取存储了所有的键的集合
Set<String> keys = map.keySet();
// 2. 遍历这个Set集合
for (String key : keys) {
// 2.1. 通过键获取值
String value = map.get(key);
// 2.2. 展示⼀下键和值
System.out.println("key = " + key + ", value = " +
value);
}
}
.使⽤forEach⽅法
这个forEach⽅法, 并不是Iterable接⼝中的⽅法。 是Map接⼝中定义的⼀个⽅法。
从功能上讲, 与Iterable中的⽅法差不多。 只是在参数部分有区别。
default void forEach(BiConsumer<? super K, ? super V> action)
/**
* 2. 使⽤forEach进⾏遍历
* @param map 需要遍历的集合
*/
private static void forEach(Map<String, String> map) {
map.forEach((k, v) -> {
// k: 遍历到的每⼀个键
// v: 遍历到的每⼀个值
System.out.println("key = " + k + ", value = " + v);
});
}
.使⽤EntrySet进⾏遍历
Entry<K, V>:
是Map中的内部静态接⼝, ⼀个Entry对象我们称为⼀个实体,⽤来描述集合中的每⼀个键值对。
/**
* 3. 使⽤entrySet进⾏遍历
* @param map 需要遍历的集合
*/
private static void entrySet(Map<String, String> map) {
// 1. 获取⼀个存储有所有的Entry的⼀个Set集合
Set<Map.Entry<String, String>> entries = map.entrySet();
// 2. 遍历Set集合
for (Map.Entry<String, String> entry : entries) {
// 2.1. 获取键
String key = entry.getKey();
// 2.2. 获取值
String value = entry.getValue();
// 2.3. 展示
System.out.println("key = " + key + ", value = " +
value);
//通过setValue可以去修改原始map的值
//映射项(键-值对)。Map.entrySet ⽅法返回映射的 collection
视图,其中的元素属于此类。
//获得映射项引⽤的唯⼀ ⽅法是通过此 collection 视图的迭代器来实
现。这些 Map.Entry 对象仅
//在迭代期间有效;更正式地说,如果在迭代器返回项之后修改了底层映
射,则
//某些映射项的⾏为是不确定的,除了通过 setValue 在映射项上执⾏
操作之外。
//entry.setValue("hello");
}
}
二 线程
1 为什么要使⽤线程?
在程序中完成某⼀个功能的时候,我们会将他描述成任务,这个任务需要在线程中完成.
2 串⾏与并发
如果在程序中,有多个任务需要被处理,此时的处理⽅式可以有串⾏和并发:
串⾏(同步):所有的任务,按照⼀定的顺序,依次执⾏。如果前⾯的任务没有
执⾏结束,后⾯的任务等待。
并发(异步):将多个任务同时执⾏,在⼀个时间段内,同时处理多个任务。
3 进程和线程
进程, 是对⼀个程序在运⾏过程中, 占⽤的各种资源的描述。
线程, 是进程中的⼀个最⼩的执⾏单元。 其实, 在操作系统中, 最⼩的任务执⾏单元并不是线程, ⽽是句柄。 只不过句柄过⼩, 操作起来⾮常的麻烦, 因此线程就是我们可控的最⼩的任务执⾏单元。
进程和线程的异同:
相同点: 进程和线程都是为了处理多个任务并发⽽存在的。
不同点: 进程之间是资源不共享的, ⼀个进程中不能访问另外⼀个进程中的数据。 ⽽线程之间是资源共享的, 多个线程可以共享同⼀个数据。 也正因为线程之间是资源共享的, 所以会出现临界资源的问题。
进程和线程的关系:
⼀个进程, 在开辟的时候, 会⾃动的创建⼀个线程, 来处理这个进程中的任务。 这个线程被称为是主线程。 在程序运⾏的过程中, 还可以开辟其他线程, 这些被开辟
出来的其他线程, 都是⼦线程。也就是说, ⼀个进程中, 是可以包含多个线程。 ⼀个进程中的某⼀个线程崩溃了,
只要还有其他线程存在, 就不会影响整个进程的执⾏。 但是如果⼀个进程中, 所有的线程都执⾏结束了, 那么这个进程也就终⽌了。
总结
程序:⼀个可执⾏的⽂件
进程:⼀个正在运⾏的程序.也可以理解成在内存中开辟了⼀块⼉空间
线程:负责程序的运⾏,可以看做⼀条执⾏的通道或执⾏单元,所以我们通常将进程
的⼯作理解成线程的⼯作
进程中可不可以没有线程? 必须有线程,⾄少有⼀个.
当有⼀个线程的时候我们称为单线程(唯⼀的线程就是主线程).
当有⼀个以上的线程同时存在的时候我们称为多线程.
4 线程的⽣命周期(会)
4.1 线程的状态:
线程的⽣命周期, 指的是⼀个线程对象, 从最开始的创建, 到最后的销毁, 中间所经历的过程。 在这个过程中, 线程对象处于不同的状态。
New: 新⽣态, ⼀个线程对象刚被实例化完成的时候, 就处于这个状态。
Runnable: 就绪态, 处于这个状态的线程, 可以参与CPU时间⽚的争抢。
Run: 运⾏态, 某⼀个线程抢到了CPU时间⽚, 可以执⾏这个线程中的逻辑
Block: 阻塞态, 线程由于种种原因, 暂时挂起, 处于阻塞(暂停)状态。 这个状态的线程, 不参与CPU时间⽚的争抢。
Dead: 死亡态, 线程即将被销毁。
4.2 . 线程的⽣命周期图

5 理解多线程(会)
简单理解(cpu单核):从宏观上看,线程有并发执⾏,从微观上看,并没有,在线程完成任务时,实际⼯作的是cpu,我们将cpu⼯作描述为时间⽚(单次获取cpu的时间,⼀般在⼏⼗毫秒).cpu只有⼀个,本质上同⼀时间只能做⼀件事,因为cpu单次时间⽚很短,短到⾁眼⽆法区分,所以当cpu在多个线程之间快速切换时,宏观上给我们的感觉是多件事同时在执⾏.
注意:
1.cpu是随机的,线程之间本质上默认是抢cpu的状态,谁抢到了谁就获得了时间⽚,就⼯作,所以多个线程的⼯作也是默认随机的.
2.在使⽤多线程时,并不是线程数越多越好,本质上⼤家共同使⽤⼀个cpu,完成任务的时间并没有减少.要根据实际情况创建线程,多线程是为了实现同⼀时间完成多件事情的⽬的.⽐如我们⽤⼿机打开⼀个app时,需要滑动界⾯浏览,同时界⾯的图⽚需要下载,对于这两个功能最好同时进⾏,这时可以使⽤多线程.
示例代码:
public class Demo4 {
//就是主线程的任务区
public static void main(String[] args) {
new Test();
/*
* ⼿动运⾏垃圾回收器
*/
System.gc();
System.out.println("main");
}
}
class Test{
@Override
/*
* 重写finalize⽅法
*/
protected void finalize() throws Throwable {
System.out.println("finalize");
}
}
6 创建线程(会)
6.1原因分析
默认情况下,主线程和垃圾回收线程都是由系统创建的,但是我们需要完成⾃⼰的功能,所以需要创建⾃⼰的线程
java将线程⾯向对象了,形成的类就是Thread,在Thread类内部执⾏任务的⽅法叫run()
6.2 . 继承Thread类
继承⾃Thread类, 做⼀个Thread的⼦类。 在⼦类中, 重写⽗类中的run⽅法,
在这个重写的⽅法中, 指定这个线程需要处理的任务。
Thread.currentThread() : 可以⽤在任意的位置, 获取当前的线程。
如果是Thread的⼦类, 可以在⼦类中, 使⽤this获取到当前的线程。
当我们⼿动调⽤run的时候,他失去了任务区的功能,变成了⼀个普通的⽅法. 当run作为⼀个普通⽅法时,内部对应的线程跟调⽤他的位置保持⼀致.
结果分析:
主线程和两个⼦线程之间是随机打印的,他们是抢cpu的关系.
通过创建Thread⼦类的⽅式实现功能,线程与任务绑定在了⼀起,操作不⽅法我们可以将任务从线程中分离出来,哪个线程需要⼯作,就将任务交给谁,操作⽅便,灵活-使⽤Runnable接⼝
示例代码:
public class Demo5 {
public static void main(String[] args) {//为了⽅便研究,先暂时不
考虑垃圾回收线程.
MyThread t1 = new MyThread("bing");
MyThread t2 = new MyThread("ying");
t1.start();
t2.start();
//⼿动调⽤run()⽅法
t1.run();
for (int i = 0; i <10; i++) {
System.out.println(Thread.currentThread().getName()+"
i:"+i);
}
}
}
class MyThread extends Thread{
String name1;
public MyThread(String name1) {
this.name1 = name1;
}
//任务区
//重写run⽅法,实现我们的功能.run就是我们的任务区
/*
* Thread.currentThread():获取的是当前的线程
* Thread.currentThread().getName():线程的名字
*/
@Override
public void run() {
for (int i = 0; i <10; i++) {
System.out.println(this.name1+"
"+Thread.currentThread().getName()+" i:"+i);
}
}
}
6.3 使用Runnable接口
在Thread类的构造⽅法中, 有⼀个重载的构造⽅法, 参数是 Runnable 接⼝。
因此, 可以通过Runnable接⼝的实现类对象进⾏Thread对象的实例化。
这⾥Thread内部默认有⼀个run,⼜通过runnable传⼊⼀个run,为什么优先调⽤的是传⼊的run?
如果该线程是使⽤独⽴的 Runnable 运⾏对象构造的,则调⽤该 Runnable 对象的 run ⽅法;否则,该⽅法不执⾏任何操作并返回。
示例代码:
public class Program {
public static void main(String[] args) {
// Runnable接⼝的匿名实现类
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out,println("⼦线程处理的逻辑");
}
};
// 实例化线程对象
Thread thread = new Thread(runnable);
}
}
6.4 优缺点对比
继承的⽅式: 优点在于可读性⽐较强, 缺点在于不够灵活。 如果要定制⼀个线
public class Demo6 {
public static void main(String[] args) {
//创建4个线程代表4个售票员
//线程与任务不分离
// SubThread s1 = new SubThread();
// SubThread s2 = new SubThread();
// SubThread s3 = new SubThread();
// SubThread s4 = new SubThread();
//
// //开启线程
// s1.start();
// s2.start();
// s3.start();
// s4.start();
//线程与任务分离测试
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);程, 就必须要继承⾃Thread类, 可能会影响原有的继承体系。
接⼝的⽅式: 优点在于灵活, 并且不会影响⼀个类的继承体系。 缺点在于可读性较差。
public class Demo6 {
public static void main(String[] args) {
//创建4个线程代表4个售票员
//线程与任务不分离
// SubThread s1 = new SubThread();
// SubThread s2 = new SubThread();
// SubThread s3 = new SubThread();
// SubThread s4 = new SubThread();
//
// //开启线程
// s1.start();
// s2.start();
// s3.start();
// s4.start();
//线程与任务分离测试
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
Thread t4 = new Thread(ticket);
//开启线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//线程与任务不分离
class SubThread extends Thread{
static int num = 20;//想让⼤家共⽤这个num
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+" num:"+
--num);
}
}
}
//线程与任务分离
class Ticket implements Runnable{
int num = 20;//想让⼤家共⽤这个num
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+" num:"+
--num);
}
}
}