重学java第一阶段(下)

day18

1
2
3
4
5
6
7
8
回顾
1·Object
toString()
equals()
hashCode()
2.hashSet这个类
3.TreeSet这个类

今天内容

1.TreeSet的底层

2.匿名内部类【开发用的】

3.内部类

4.map

1.二叉树的了解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
通过查阅API我们得知TreeSet集合是基于TreeMap的实现,而TreeMap是基于二叉树(红黑树)结构,也就是说TreeSet集合的底层使用的二叉树(红黑树)结构。

树结构:它也是数据结构中的一种。在计算机领域中树结构指的是倒立的树。

树结构存储的数据,每个数据也需要节点来保存。

而TreeSet集合底层是二叉树的数据结构,什么是二叉树呢?

二叉树:每个节点的下面最多只能有2个子节点。

说明:最多表示一个节点下面可以有两个子节点或者一个子节点或者没有子节点。

在二叉树的根节点左侧的节点称为左子树,在根节点的右侧的节点称为右子树。

既然已经得知TreeSet集合底层是二叉树,那么二叉树是怎样存储数据的呢?是怎样保证存储的数据唯一并有序的呢?

二叉树的存储流程:

当存储一个元素的时候,如果是树的第一个元素,这个元素就作为根节点。

如果不是第一个元素,那么就拿要存储的元素与根节点进行比较大小:

大于根元素:就将要存储的元素放到根节点的右侧,作为右叶子节点。

等于根元素:丢弃。

小于根元素:就将要存储的元素放到根节点的左侧,作为左叶子节点。

总结:二叉树是通过比较大小来保证元素唯一和排序的。
20 10 31 5 13 23 51

http://data.biancheng.net/view/192.html

案例:

​ 使用TreeSet存储Employee对象,比较两个属性

​ int age, int weight 先按照年龄进行升序排,如果年龄相等的话,按照体重升序排

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.qfedu.a_treeset;

import java.util.Set;
import java.util.TreeSet;

class Employee implements Comparable<Employee>{
String name;
int age;
int weight;

public Employee(String name, int age, int weight) {
this.name = name;
this.age = age;
this.weight = weight;
}

@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", weight=" + weight +
'}';
}

@Override
public int compareTo(Employee o) {
//先按照年两比,如果年龄相等 就比较体重
int num = this.age - o.age;
if (num == 0) {
int num1 = o.weight - this.weight;
return num1;
}
return num;
}
}
public class Demo2 {
public static void main(String[] args) {
Set<Employee> set = new TreeSet<>();
set.add(new Employee("广坤", 35, 78));
set.add(new Employee("二贝", 26, 70));
set.add(new Employee("赵四", 35, 72));
set.add(new Employee("彩云", 35, 79));
set.add(new Employee("鸡哥", 32, 59));
set.add(new Employee("正经博", 32, 59));
System.out.println(set);
}
}

案例:

​ TreeSet里面存的是Dog类,

​ 两个属性: String name, int age

​ 先按照字符串的字典顺序排,然后字符串相等的话,在按照年龄排

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.qfedu.a_treeset;

import java.util.Set;
import java.util.TreeSet;

class Dog implements Comparable<Dog>{
String name;
int age;

public Dog(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

@Override
public int compareTo(Dog o) {
//先按照字典的顺序进行排,如果字符串相等再按照年龄升序排
int num = this.name.compareTo(o.name);
if (num == 0) {
//字符串相等的情况,又要比较年领
int num1 = this.age - o.age;
return num1;

}
return num;
}
}
public class Demo3 {
public static void main(String[] args) {
Set<Dog> set = new TreeSet<>();
set.add(new Dog("彩云", 5));
set.add(new Dog("旺财", 2));
set.add(new Dog("大黄", 6));
set.add(new Dog("大黄", 3));
set.add(new Dog("大黄", 4));
System.out.println(set);
}
}

总结:使用TreeSet的时候需要类实现一个接口 Comparable这个接口去做比较排序

但是就只有这一种方式进行排序吗?不是的!!!还有一种比较器的写法Comparator这个接口

2.使用比较器将数据存储到TreeSet中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.qfedu.b_comparator;

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

class Student {
String name;
int age;

public Student(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class MyComparator implements Comparator<Student> {

@Override
public int compare(Student o1, Student o2) {
int num = o1.age - o2.age;
return num;
}
}
public class Demo1 {
public static void main(String[] args) {
//如果想要使用比较器的写法 必须再new TreeSet的时候
//加上比较器对象
//TreeSet 有一个有参构造,有参构造的方法是Comparwator的对象
//Comparator是一个接口 不能实例化,咋办?再写一个类去实现这个接口
Set<Student> students = new TreeSet<>(new MyComparator());
students.add(new Student("维一", 23));
students.add(new Student("永康", 19));
students.add(new Student("赵娜", 18));
students.add(new Student("运铎", 28));
students.add(new Student("佳祥", 36));
System.out.println(students);
}
}

总结:

1
2
3
4
5
ArrayList:就是单纯的add
LinkedList: 也是单纯的add
HashSet: 不单纯 得重写equals 和hashCode 方法
TreeSet:不单纯 得在类中去实现Comparable这个接口 让类具有比较排序功能
开发中使用ArrayList

3.匿名内部类

为了减少代码量

3.1基于抽象类的匿名内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.qfedu.c_anno;

//声明一个抽象列类
abstract class Person {
public abstract void eat();
public void sleep() {
System.out.println("好想逃,然后去睡觉!!!");
}
}
//常规来讲,新建一个类 去继承抽象类。然后实例化继承的抽象类的类
//class Man extends Person {
//
// @Override
// public void eat() {
// System.out.println("好饿,想吃肉!!!");
// }
//}
public class Demo1 {
public static void main(String[] args) {
//匿名内部类:在实例化对抽象类同时并重写抽象方法
Person person = new Person(){
@Override
public void eat() {
System.out.println("吃吃喝喝");
}
};
person.eat();
person.sleep();

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.qfedu.c_anno;

abstract class Animal{
public abstract void call();
public abstract void call1();

}
public class Demo2 {
public static void main(String[] args) {
new Animal(){
@Override
public void call() {
System.out.println("哇哇叫!!!");
}

@Override
public void call1() {
System.out.println("哈哈叫");
}
}.call();
new Animal(){
@Override
public void call() {
System.out.println("哇哇叫!!!");
}

@Override
public void call1() {
System.out.println("哈哈叫");
}
}.call1();
}
}

3.2基于接口的匿名内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.qfedu.c_anno;

interface A {
void testA();

}
public class Demo3 {
public static void main(String[] args) {
// A a = new A(){
//
// @Override
// public void testA() {
// System.out.println("嘻嘻哒");
// }
// };
// a.testA();
new A(){

@Override
public void testA() {
System.out.println("哈哈");
}
}.testA();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.qfedu.c_anno;

import java.util.Comparator;
import java.util.TreeSet;
import java.util.Set;

class Student {
String name;
int age;

public Student(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Demo4 {
//存到TreeSet里面
public static void main(String[] args) {
Set<Student> set = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int num = o1.age - o2.age;
return num;
}
});
set.add(new Student("维一", 23));
set.add(new Student("永康", 19));
set.add(new Student("赵娜", 18));
set.add(new Student("运铎", 28));
set.add(new Student("佳祥", 36));
System.out.println(set);
}
}

真实开发的时候,一个方法参数是一个接口对象,不用再新建一个类去实现这个接口,直接方法中去new 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.qfedu.c_anno;

interface B {
void eat();
}
public class Demo5 {
public static void main(String[] args) {
test(new B() {
@Override
public void eat() {
System.out.println("吃锦旗");
}
});
}
public static void test (B b) {
b.eat();
}
}

4.内部类

从字面意思来理解:在类的内部创建一个类,这个类叫内部类

4.1成员内部类

在类的方法的外面再类中,就是成员内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.qfedu.d_inner;

class MemberDemo {
String name = "张三";
int age = 20;

public void printf() {
System.out.println("打印着玩");
}
class Inner {//就是内部类
String name = "李四";
//在Inner这个类中可以访问外部类的属性和方法
public void test () {
printf();//打印着玩
System.out.println(age);//20
System.out.println(name);//李四
//如果访问外部特定的属性的时候: 类名.this.属性
System.out.println(MemberDemo.this.name);//张三
}
}

}
public class Demo1 {
public static void main(String[] args) {
//成员内部类的创建步骤:
//1.实例化外部类 类对象
MemberDemo memberDemo = new MemberDemo();
//2.实例化内部类对象,但是new得改为 外部对象.new
MemberDemo.Inner inner = memberDemo.new Inner();
inner.test();

}
}

day19

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
1.匿名内部类的写法
针对于抽象类和接口的
现在直接new,但是在new 接口的时候 一定重写抽象的方法
new A (){
public void test () {
System.out.println("测试");
}
}.test();
真实开发的时候:
一个接口或者抽象类会作为方法的参数。
在之前新建一个类去实现一个接口。但是现在学完匿名内部类之后
直接在方法中直接new
2.成员内部类的写法
class Person {

class Inner {

}
}
3.String类下面的方法
重要

4.List接口下面的方法
add
remove
get
size
set
5.比较器在TreeSet中的使用
Set<Student> set = new TreeSet<>(new Comparator<Student> () {
public int compare (Student o1, Student o2) {
return o1.age - o2.age;
}
});
6.八大基本数据类型所对应得包装类
int Integer
byte Byte
short Short
long Long
float Float
double Double
char Character
boolean Boolean
7.HashSet和TreeSet区别

今天的内容

1.Map

2.File类

1.Map集合

地图, 通过一个点可以找到一个具体的位置 映射

map集合也是存数据的

双边队列

Interface Map<K,V>

k: key 键

v: value 值

键值对

意味着咱们map集合中存的数据是键值对像=形式的数据

{0001=家豪, 002=志成, 003=王腾飞}

–|HashMap

–|TreeMap

1.1Map集合中常用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
增:
V put(K key, V vlaue);添加键值对的数据到map集合中
void putAll(Map<? extends K> k, Map<? extends V> v);将一个map集合存放到另外一个map集合中

删:
V remove (K key);通过键删除键值对的数据,返回值是值
改:
V put(K key V value);当键值存在的时候,会将value值覆盖掉的
查:
int size(); 查看集合中元素的个数
boolean isEmpty();判断是否为空,如果不为空返回的是false
boolean containsKey();是否包含这个键
boolean containsValue();是否包含这个值
重要的方法:
V get(K key);通过键获取值
Set<K> keySet();获取map集合中的键,然后存到set集合中
Collection<V> values(); 获取map集合中值,存到了Collection集合中
Set<Map.Entry<K,V>> entrySet();将map集合的键值对,存到了set集合
Map.Entry这个接口的方法
getKey:返回键值对的键
getValue:返回键值对的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.qfedu.a_map;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Demo1 {
public static void main(String[] args) {

Map<String, String> map = new HashMap<>();
map.put("002", "老邢");
map.put("001", "骚磊");
map.put("003", "老万");
//{001=老邢, 002=骚磊, 003=老万}
System.out.println(map);
//键必须是唯一的,因为底层使用的hash算法的操作
//value值是会被替换的,当键一样的时候

map.put("002", "二贝");
System.out.println(map);
Map<String, String> map1 = new HashMap<>();
map1.put("004", "小邢");
map1.put("005", "小骚磊");
map1.put("006", "小老万");
map.putAll(map1);
System.out.println(map);
//{001=骚磊, 002=二贝, 003=老万, 004=小邢, 005=小骚磊, 006=小老万}
System.out.println(map.remove("001"));//骚磊
System.out.println(map);//删除完以后,看看还有咩有这个键值对
map.put("006", "小小万");//修改
System.out.println(map);
//查询
System.out.println(map.size());//5
System.out.println(map.isEmpty());//false
System.out.println(map.containsKey("002"));//true
System.out.println(map.containsValue("大狗蛋"));//false


//获取所有的键 存到set集合中了
Set<String> strings = map.keySet();
System.out.println(strings);

Collection<String> values = map.values();
System.out.println(values);

//entrySet
Set<Map.Entry<String, String>> entries = map.entrySet();
System.out.println(entries);
//将数据取出来
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry);
System.out.println(entry.getKey());
System.out.println(entry.getValue());
System.out.println("--------");
}

}
}

1.2Map集合中的value 存的是对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.qfedu.a_map;

import java.util.*;

class Student {
String name;
int age;

public Student(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Demo2 {
public static void main(String[] args) {
List<Student> list = new ArrayList<>();
list.add(new Student("张三", 24));
list.add(new Student("狗蛋", 26));
list.add(new Student("财运", 28));
list.add(new Student("彩云", 23));

List<Student> list1 = new ArrayList<>();
list1.add(new Student("二贝", 24));
list1.add(new Student("老邢", 26));
list1.add(new Student("秦始皇", 28));
list1.add(new Student("汉武帝", 23));
Map<String, List<Student>> map1 = new HashMap<>();
map1.put("001", list);
map1.put("002", list1);

//以上在存值
//取值
Collection<List<Student>> values1 = map1.values();
for (List<Student> students : values1) {
for (Student student : students) {
System.out.println(student.name);
}
}
System.out.println("======");

Map<Integer, Student> map = new HashMap<>();
map.put(1, new Student("张三", 24));
map.put(2, new Student("张三", 24));
map.put(3, new Student("狗蛋", 26));
map.put(4, new Student("李四", 21));
System.out.println(map);
//以上存值
//取值

//取键
Set<Integer> key = map.keySet();
System.out.println(key);
Collection<Student> values = map.values();
System.out.println(values);
for (Student value : values) {
System.out.println(value.name);
}

System.out.println("=====");
//使用entrySet
Set<Map.Entry<Integer, Student>> entries = map.entrySet();
for (Map.Entry<Integer, Student> entry : entries) {
System.out.println(entry.getValue().name);
}
}
}

1
2
3
4
5
可以自己百度先自己看一下,看看能不能看懂!!! jdk 1.8的  HashMap源码 底层
总结:
开发中用
List==》ArrayList
Map===>HashMap

下面来看一个hashset和treeset的区别

因为都是set的子类,Set具有元素不可重复性,所以TreeSet和hashset都不可放2个相同的元素
TreeSet底层是TreeMap实现的 ,很多api都是利用TreeMap来实现的
HashSet底层是HashMap实现的,很多api都是利用HashMap来实现的

TreeSet
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0

HashSet集合不能存储重复的元素,那么元素之间是否重复,HashSet是根据什么机制去判断的呢?

HashSet在添加一个元素时(比如此时添加的是”a”这个元素),都会将该元素与set中所遍历到的每个元素作比较,比较的过程是这样的:先用该元素的hashCode值与遍历到的每个元素的hashCode作比较,如果hashCode不相等,则直接添加;若hashCode的值一样,则继续用该元素的equals()方法比较(是被添加的equals()方法,与之比较的元素作为参数),如果equals()方法得到的值是一样的,不再添加,如果equals()的值是不一样的,就会将该对象添加到其他内存地址(重新计算出不一样的hashCode)。

HashSet

HashSet有以下特点
 不能保证元素的排列顺序,顺序有可能发生变化
 不是同步的
 集合元素可以是null,但只能放入一个null
当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。
简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相 等
注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对 象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。

HashSet是基于HashMap实现的,元素的值存储在key上,value的值所有元素都一样,都是这个 private static final Object PRESENT = new Object();

TreeSet类

TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。
自然排序
自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。
obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。
如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0
定制排序
自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法。

最重要:

1、TreeSet 是二叉树实现的,Treeset中的数据是自动排好序的,不允许放入null值。

2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束。

3、HashSet要求放入的对象必须实现HashCode()方法,放入的对象,是以hashcode码作为标识的,而具有相同内容的 String对象,hashcode是一样,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例

2.File类

文件和文件夹(文件路径)的抽象表示,是专门来出来u磁盘上面的文件或者文件夹的

之前都是手动创建,现在可以借助Java封装好的类创建文件夹和文件。

路径问题:

​ 相对路径:

​ 得有一个参照物

​ ./当前工作目录

​ ../上一级路径的目录

​ ../../上两级目录

​ 绝对路径:

​ 磁盘的跟路径一级一级的往下找

​ C:\ccc\ddd\1.png

2.1File 类的构造方法

File(String pathname)

通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。

File(String parent, String child)

从父路径名字符串和子路径名字符串创建新的 File实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.qfedu.b_file;

import java.io.File;

public class Demo1 {
public static void main(String[] args) {
//可以将1.txt 抽象成一个Java代码的对象
File file = new File("c:/aaa/1.txt");
File file2 = new File("c:\\aaa\\1.txt");
//Java中 看系统
/**
* 1./ 在windows系统和linux 都是可以的!!!
* 2.\ 只有在windows系统下面可以的
*/
/**
* static String separator
* 与系统相关的默认名称 - 分隔符字符,以方便的方式表示为字符串。
* 当你是window系统的时候 就是\
* 当你的代码在linux /
*/

System.out.println(File.separator);
//为了保证你的代码在任意的系统下面 路径的分割符是都可以使用
//可以咋写代码?
File file3 = new File("c:" + File.separator + "aaa" + File.separator + "1.txt");
System.out.println(file3);
System.out.println("=====");
System.out.println(file);
//File(String parent, String child)
File file1 = new File("c:/aaa/", "1.txt");
System.out.println(file1);


}
}

2.2File类下面的方法

boolean createNewFile();创建一个文件 返回值是布尔类型的数据

​ 1.如果文件路径不存在会报错

​ 2.如果文件名字已经存在,返回false

​ 3.磁盘坏了,创建不了

boolean mkdir();创建单级路径

boolean mkdirs();创建多级路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.qfedu.b_file;

import java.io.File;
import java.io.IOException;

public class Demo2 {
public static void main(String[] args) throws IOException {
File file = new File("c:/aaa/1.txt");
System.out.println(file.createNewFile());
File file1 = new File("c:/aaa/bbb");
System.out.println(file1.mkdir());//单级路径

File file2 = new File("c:/aaa/ccc/ddd");
System.out.println(file2.mkdirs());//多级路径


}
}

删除文件或者文件夹

boolean delete();立即删除文件,常用的

void deleteOnExit();不会立即删除,是程序退出以后才删除的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.qfedu.b_file;

import java.io.File;
import java.util.Scanner;

public class Demo3 {
public static void main(String[] args) {
File file = new File("c:/aaa/1.txt");
System.out.println(file.delete());
//删除一个非空的文件夹,是删除不了的,咋办?递归
File file1 = new File("c:/aaa/ccc");
System.out.println(file1.delete());

File file2 = new File("c:/aaa/bbb");
System.out.println(file2.delete());
File file3 = new File("c:/aaa/ccc/ddd");
file3.deleteOnExit();
//如果保证程序不退出
new Scanner(System.in).nextInt();

}
}

File对象的判断方法,比较常用的

boolean isFile(); 是否是文件【常用】

boolean isDirectory();是否是文件夹【常用】

boolean isHidden();是否是隐藏文件

boolean isAbsolute();是否是绝对路径

boolean exists();判断文件或者文件夹是否存在【重要】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.qfedu.b_file;

import java.io.File;
import java.io.IOException;

public class Demo4 {
public static void main(String[] args) throws IOException {
File file = new File("c:/aaa");
System.out.println(file.isFile());//false
File file1 = new File("c:/aaa/2.gif");
System.out.println(file1.isFile());//true
System.out.println(file.isDirectory());//true
System.out.println(file1.isDirectory());//false
File file2 = new File("c:/aaa/2.txt");
System.out.println(file2.isHidden());//true
System.out.println(file1.isHidden());//false
System.out.println(file.isAbsolute());//true

File file3 = new File("./");
System.out.println(file3.isAbsolute());//false

System.out.println(file1.exists());//true
if (!file1.exists()) {
file1.createNewFile();
}
}
}

返回值是String类型的数据的

String getName(); 获取文件或者文件夹的名字的

String getPath();获取当前对象的路径的

String getParent();获取当前文件对象的上一级的目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.qfedu.b_file;

import java.io.File;

public class Demo5 {
public static void main(String[] args) {
File file = new File("c:\\aaa\\2.gif");
System.out.println(file.getName());//2.gif
System.out.println(file.getParent());//c:\aaa
System.out.println(file.getPath());//c:\aaa\2.gif
System.out.println(file);



}
}

返回是long类型数据的

long lenght();返回值是文件占用的字节数

long lastModified();获取当前文件最后一次修改的时间

​ 这个是毫秒,除以1000 变成秒,才是时间戳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.qfedu.b_file;

import java.io.File;

public class Demo6 {
public static void main(String[] args) {
File file = new File("c:/aaa/8.txt");
System.out.println(file.length());//48

//1659687558179
//从1970 1月1日 0时 0分 0 秒 ~ 2022 8 5 16:19
//的毫秒数
System.out.println(file.lastModified());

}
}

File[] listFiles(); 获取当前文件夹下面的所有的=文件

String[] list();获取当前文件夹下面的所有文件的名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.qfedu.b_file;

import java.io.File;

public class Demo7 {
public static void main(String[] args) {
File file = new File("c:/");
File[] files = file.listFiles();
for (File file1 : files) {
//列出来的是对象 File
System.out.println(file1);
}
System.out.println("=======");
String[] list = file.list();
for (String s : list) {
//文件的名字
System.out.println(s);
}
}
}

需求:

1
2
删除某一个目录下面的所有的文件
当你不知道你有多少个文件夹和文件的时候,咱们可以递归的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.qfedu.b_file;

import java.io.File;

public class Demo9 {
public static void main(String[] args) {
File file = new File("c:/aaa/bbb");
del(file);
}
public static void del (File file) {
//所有文件先取出来
File[] files = file.listFiles();
/**
* c:\aaa\bbb\1.txt
* c:\aaa\bbb\ddd
* file1是ddd 是文件夹
* 1.txt删除了
* 再次执行del(file1) ddd文件夹
* eee
* 2.txt
* 再次执行del(eee) eee文件夹了
*
*
*/
for (File file1 : files) {
System.out.println(file1);
if (file1.isDirectory()) {//是文件夹
del(file1);//递归

}else {//是文件
file1.delete();
}
}
}
}

day20

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
1.File类下面的方法
createNewFile 创建一个新的文件
mkdir 创建一个单级的文件夹
mkdirs创建多级的文件夹
delete 删除文件或者文件夹
isFile 判断是否是文件
isDirectory 判断是否是文件夹
isHidden 判断是否是隐藏文件
isAbsolute 判断是否是绝对的路径
exists 判断文件是否存在(需要用的)
getName 获取文件或者文件夹的名字
getPath 获取当前file对象的路径的
getParent 获取当前文件的上一级目录
length 文件占用的字节数 byte
lastModified 获取当前文件被修改的时间 除以1000等于时间戳 本身返回值是毫秒数
listFile 返回值是File数组 将当前的文件夹下面的所有的文件或者文件夹返回一个File数组
list 返回值是String数组,将当前的文件夹下面的所有的文件或者文件夹String
2.Map下面的方法
Map<String, String> map = new HashMap<>();
增:
put(K key, V value)
putAll();
删:
remove(K key); 通过键删除值
改:
put(K key, V value) 只要键存在的话就是会修改值
查:
size()
containsKey();
containsValue();
keySet() 获取集合中的键 存到set集合中
values() 获取集合中的值 存到Collection集合中
entrySet() 将一个键值对 封装一个entry 存到咱们的set集合中

3.List下面的方法
add

4.String下面的方法

今天的内容

IO流

1.IO流

以后开发中会遇到文件的上传和下载,都是需要用到IO流

咱们电脑上面所有的文件, 文档 音频 图片 视频 等都是可以进行读和写的。他们咋读的?

咋写的? 一下子就写到咱们项目中了吗?不是,依靠流的形式进行读 和写。很抽象

在读取的时候,会将咱们的文件(音频 视频 等)变成流的形式 一点一点来处理的 拆解开来出来的。

1.1缓冲的概念

看视频有点卡?暂停一下,加载缓冲一下。

快递:送到物流中转站,然后分批次的发。物流中转站就是缓冲的概念。

IO流 的本质就是对电脑的文件进行读和写的

计算机通过CPU内存读取磁盘上面的文件数据,一次读取1字节。但是可以加上缓冲的概念

每次读取4kb。效率会高点的,咱们可以测试一下。效率是不是确实高了。

1.2IO流的分类

咱们今天的学习紧紧围绕着:

​ 1.从磁盘读取数据到内存(Java代码) 磁盘=》代码中 输入流

​ 2.从内存(Java代码 String str = “狗蛋很帅”)写入数据到磁盘某一个文件 代码=》磁盘 输出流

​ 参照物:是内存,就是Java代码。

​ 输入流: 从磁盘读取到内存

​ 输出流:从内存写入到磁盘

I:

​ input: 输入 从磁盘读取数据到内存

​ 使用input输入流的应用场景是啥:比如 磁盘上面有一个1.txt文件,将1.txt文件的内容读取到Java内存中。使用的是输入流。

​ 输入流分为两种:

​ 1.字节输入流 FileInputStream

​ 2.字符输入流

​ 输出流分为两种:

​ 1.字节输出流 FileOutputStream

​ 2.字符输出流

1.2.1字节输入流

讲一个新的知识点,你最起码先知道这个知识点能干嘛?

字节输入流:磁盘上面有一个1.txt文件,现在要将1.txt文件中的内容读取到内存(Java代码中)

Java给咱们封装好了类: FileInputStream 文件输入流(字节输入流)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.qfedu.a_fileinputstream;

import java.io.*;

public class Demo1 {
public static void main(String[] args) throws IOException {
//将磁盘上面的c:/aaa/1.txt文件这个数据读取到内存(Java)中
//加上缓冲的效果,这种很牛逼啊,将磁盘上面的东西弄到咱们Java代码中
//1.创建File对象,是咱们本地磁盘文件的一个File对象
//为啥要创建这个File对象?因为要读取1.txt这个文件的里面的
//内容,首先先找到文件再说啊。
File file = new File("c:/aaa/1.txt");
//2.创建文件字节输入流对象,来操作1.txt
//FileInputStream(File file)
//通过打开与实际文件的连接创建一个 FileInputStream ,
// 该文件由文件系统中的 File对象 file命名。
//将c:/aaa/1.txt文件转为字节输入流的形式,之后可以按照流的形式读取到内存中
FileInputStream fis = new FileInputStream(file);
//3.fileInputStream 这个流不具备缓冲的效果。
//但是加上缓冲的效果!!! 咋办?使用另外一个流
//BufferedInputStream
//A BufferedInputStream为另一个输入流添加了功能,即缓冲输入
//咋加?将FileInputStream传给BufferedInputStream 此时
//FileInputStream 就具备缓冲的功能了!!!
BufferedInputStream bis = new BufferedInputStream(fis);
//使用字节缓冲流操作咱们的1.txt
//当创建BufferedInputStream时,
// 将创建一个内部缓冲区数组,这个数组是什么数据类型的?
//5.创建一个缓冲区的数组 byte 为啥是byte类型的数组
//因为是字节输入流 所以是byte byte 就是字节
//当缓冲数组是2个字节的时候,每次读取的时候存到缓冲数组中,只存2个字节
//发现数据一次性读取不完?咋办?咋解决?
byte[] buf = new byte[1024 * 4];//缓冲区4096字节
//现在数组是空的
//6.读取数据
//public int read(byte[] b)
// throws IOException
//从输入流读取一些字节数,并将它们存储到缓冲区b 。
// 实际读取的字节数作为整数返回
//将1.txt文件的内容 读取到缓冲数组中 buf 里面
//之前buf是一个空的数组,现在通过read方法将1.txt文件中的
//内容读取到了 buf空的数组中
// int read = bis.read(buf);
// System.out.println(read);
//6.调用read方法之后,buf 数组就有值了 能不能打印一下
//借助于String类来展示byte数组的内容
//buf 给字符串 为了展示字节数组中的内容的
//0 偏移量 buf这个数组中从哪开始取值 从下标为0 的地方取值
//read 取6个
//如果b的长度为零,则不会读取字节并返回0 ;
// 否则,尝试读取至少一个字节。
// 如果没有字节可用,因为流在文件末尾,则返回值-1
int length = -1;
//如果length=-1 那么就会到达流的末尾 就证明后面没哟数据
//循环结束,不要再读了
while ((length = bis.read(buf)) != -1) {
//循环4次
System.out.println("狗蛋");
//数组中的东西要展示出来 使用String
//将磁盘上面的内容 读取到 =》 内存 buf
System.out.println(new String(buf, 0, length));
}
//7.流是需要关闭的
bis.close();
fis.close();



}
}

知道字节输入流是干嘛的?可以将磁盘上面的某一个文件读取到Java代码中(内存)

固定的写法!!!但是你得知道每行代码是啥意思

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.qfedu.a_fileinputstream;

import java.io.*;

public class Demo2 {
public static void main(String[] args) throws IOException {
//1.创建File对象
File file = new File("c:/aaa/7.txt");
//2.创建字节输入流,将7.txt扔给 fis对象了
FileInputStream fis = new FileInputStream(file);
//3.创建字节缓冲流, fis又扔给 bis了 现在咱们的数据在bis里面了

BufferedInputStream bis = new BufferedInputStream(fis);
//4.声明一个缓冲数组,将bis里面的数据读取出来 赋值给这个
//缓冲数组
byte[] buf = new byte[4 * 1024];//4096字节
//5.开始使用read方法进行读取,读取的时候使用while循环

//如果7.tx文件中的字节数超过了4096个字节,需要使用循环,再次的读取
int length;
//现在数据都在bis里面,咱们要把bis里面的数据读取buf这个数组中
while ((length = bis.read(buf)) != -1) {
//byte数组有值以后,咱们可以打印一下看看byte数组中的数据
System.out.println("循环执行");
//将字节数组转为字符串,的目的是为了看一下字节数组中的数据
//并没有实际的意义
System.out.println(new String(buf, 0, length));
}
//6.关闭流
bis.close();
fis.close();



}
}

案例:读取你们盘符下面的一个txt文档,打印在idea控制台。写完以后,将代码直接发到扣扣群里

好好想想这个流程是咋走的?

想象咱们班门口放了一袋大米(就是磁盘的本地文件,文件的内容),现在我要求把门口给我运到我的讲台(Java的内存),不能使用袋子直接搬运?

你们咋解决?可以找一个杯子(就是缓冲数组)。一杯子一杯子的运。能接受不?

比如磁盘上面的380字节的数据, 咋把读取到内存中? 弄一个缓冲数组,每次读取3字节

BufferedInputStream 其实FileInputStream其实一次读取一个字节的,但是使用BufferedInputStream 了之后,可以搞一个缓冲的数组,一次可以读取多个,那么循环次数就会减少,代码的效率就会提升。循环1000遍 和循环1遍的效率 循环1遍的效率,下午演示 加缓冲流和不加缓冲流哪个效率高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
abcdefg

每次读取3个字节
第一次 abc
while (length = 3 != -1) { true
sout(abc)
}
第二次 def
while (length = 3 != -1) {true
sout(def)
}
第三次
while ((length = 1)!= -1 ) {true
sout(g)new String(buf, 0, 1)
}
当到达文件的末尾的时候, 后面没有东西了,文件的末尾了,到达流的末尾了。返回值是-1
while ((length = -1) != -1) { false 循环结束
sout(abc)
}

回顾

1
2
3
4
5
6
7
1.上午讲的是字节输入流
将磁盘上面的某一个文件的内容读取到内存中
1、实例化一个file对象 找到本地磁盘的一个文件对象
2、实例化一个FileInputStream
3、加上缓冲流 BufferedInputStream
4、使用缓冲流对象 将内容读取read(buf)到内存缓冲数组中 使用while循环来读取
5.关闭流
1.2.2字节输出流

将Java中数据写入到磁盘上面

内存-》磁盘 输出流

讲什么案例? 比如Java代码中有一个字符串 String str = “唉,愁人”;将这个字符串写入磁盘某一个文件中

FileOutputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.qfedu.b_fileoutstream;

import java.io.*;

public class Demo1 {
public static void main(String[] args) throws IOException {
//将Java代码中的一个字符串写入到咱们的磁盘的某一个文件中
//1.创建File对象,文件路径对象 告知编译器要把数据写到哪个文件中
File file = new File("c:/aaa/2.txt");
//2.实例化字节输出流对象
FileOutputStream fos = new FileOutputStream(file);
//3.对FileOutputStream 加上缓冲的效果
//猜测一下 FileInputStream所对应的缓冲流 BufferedInputStream
//FileOutputStream 所对应的缓冲流 BufferedOutputStream
BufferedOutputStream bos = new BufferedOutputStream(fos);
//4.将一个字符串写入到磁盘中
String str = "大家再等我一天,后天见面!!!";
// write(byte[] b)
//咱们的参数是字节数组,咋办?str是一个字符串 但是参数是一个字节数组,咋解决?
//str是字符串 不能强转 将字符串转为字节数组,没有讲过!!!
//将str字符串转为字节数组
byte[] bytes = str.getBytes();
bos.write(bytes);
//5.写完以后一定要刷新流然后关闭流
bos.flush();//没有实际的意义,可以不写的
//6.关闭流
//在输入流的时候一定是先开的后关,后开的先关
bos.close();
fos.close();




}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.qfedu.b_fileoutstream;

import java.io.*;

public class Demo2 {
public static void main(String[] args) throws IOException {
//将内容写入到3.txt文件中
File file = new File("c:/aaa/3.txt");
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
//开始写了
//void write(int b);
//传参是一个int类型,但是真正写入的时候是int类型所对应的字符
bos.write(97);//开发不用!!!
bos.flush();
bos.close();
fos.close();


}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.qfedu.b_fileoutstream;

import java.io.*;

public class Demo3 {
public static void main(String[] args) throws IOException {
File file= new File("c:/aaa/4.txt");
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
String str = "abcdefg";//7个字节
//write(byte[] b,int off,int len)
byte[] bytes = str.getBytes();
//写一个到缓冲流 从0开始偏移 写入2个字节

//可以借助于循环往里面写!!!
bos.write(bytes, 0, 2);//ab
bos.write(bytes, 2, 2);//cd
bos.write(bytes, 4, 2);//ef
bos.write(bytes, 6, 1);//ef
bos.flush();
bos.close();
fos.close();

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.qfedu.b_fileoutstream;

import java.io.*;

public class Demo4 {
public static void main(String[] args) throws IOException {

BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("c:/aaa/4.txt")));
bos.write("xixida".getBytes());
bos.flush();
bos.close();

}
}

针对于咱们的讲的字节输入流和字节输出流来一个案例

将c:/bbb文件夹下面一个视频 (源文件) 复制到c:/aaa文件夹下面

思路:将bbb文件夹下面的视频先读取到内存,然后再从内存写入磁盘aaa文件夹下面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
缓冲数组  byte[] buf = new byte[4];
视频由字节组成的 一次读取 4096字节 直到最后一次 不一定4096字节
最后一次3456 字节 write(buf, 0, 3456)
相当与将所有字节重新拼接了一下
读取的时候每次读取4096个放到咱们的缓冲数组中,然后立马写入另外磁盘文件下面
直到读取完,写入完即可


qwertyuiopasd 13字节

//第一循环
while ((length=bis.read(buf)) != -1) {//buf 里面 有 qwer
bos.write(buf, 0, 4); 文本里面有 qwer
}
第二次循环
while ((length=bis.read(buf)) != -1) {//buf 里面 有 tyui
bos.write(buf, 0, 4); 文本里面有 qwertyui
}
第三次循环
while ((length=bis.read(buf)) != -1) {//buf 里面 有 opas
bos.write(buf, 0, 4); 文本里面有 qwertyuiopas
}
第四次循环
while ((length=bis.read(buf)) != -1) {//buf 里面 有 d
bos.write(buf, 0, 1); 文本里面有 qwertyuiopasd
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.qfedu.c_io;

import java.io.*;

public class Demo1 {
public static void main(String[] args) throws IOException {
copyVideo1();
}
//现在咱们讲的这个是带有缓冲的,看看带有缓冲视频的复制的耗时多少 2610ms
//写一个方法将bbb文件夹下面的3.map4格式的视频复制到aaa文件夹下面
//用带缓冲的,效率高
public static void copyVideo () throws IOException {
//获取系统的当前的毫秒数
long start = System.currentTimeMillis();
//1.创建字节缓冲输入流 将磁盘的数据读取到内存
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("c:/bbb/3.mp4")));

//2.创建字节缓冲输出流 从内存写入到的文件 源文件是mp4格式,写入到的也必须是mp4格式的
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("c:/aaa/goudan.mp4")));

//3.准备一个字节数组 缓冲区数组
byte[] buf = new byte[4 * 1024];//代码走到这一步是空的
//4.从磁盘读取数据到内存
int length = -1;
//这个视频很大,依靠循环来读取,每次读4 * 1024字节
while ((length = bis.read(buf)) != -1) {//从磁盘上读取数据到buf缓冲数组中
//每次循环 buf数组中有值,顺便依靠循环写入到磁盘
System.out.println("qwer");
bos.write(buf, 0, length);

}
//5.关闭流
bos.close();
bis.close();
//复制完以后再得到一个毫秒数,相减 就得到复制一个视频需要的时间
long end = System.currentTimeMillis();
System.out.println(end - start);


}
//下面来=看一下不带缓冲的流的读写
public static void copyVideo1 () throws IOException {
long start = System.currentTimeMillis();
FileInputStream fis = new FileInputStream(new File("c:/bbb/3.mp4"));
FileOutputStream fos = new FileOutputStream(new File("c:/aaa/goudan.mp4"));
int length = -1;
while ((length = fis.read()) != -1) {
fos.write(length);
}
fos.close();
fis.close();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}

1.2.3字符输入流【非重点】

也是输入流,将磁盘的某一个文件读取到内存

FileReader:

​ 是一个阅读字符文件的便利类,是专门处理字符文件的,比如txt文件。音频视频图片

不能使用这个流。

是从字节流到字符流的桥:它读取字节,并使用指定的charset将其解码为字符 。 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集

牵涉到解码,底层是字节流,但是会解码为字符。如果解码失败就意味着咱们读取失败了

一般不会使用字符流操作图片 音频 视频等,因为牵涉到解码。会出问题!!!

开发一般使用字节流!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.qfedu.d_FileReader;

import java.io.*;

public class Demo1 {
public static void main(String[] args) throws IOException {
//将c盘下面的aaa文件夹下面的1.txt文件的内容 读取到Java内存中
//和字节流一模一样
//1.新建一个File对象
File file = new File("c:/aaa/1.txt");
//2.新建FileReader 对象 字符输入流
FileReader fr = new FileReader(file);
//3.FileRFeader没有缓冲的效果,可以加上缓冲的效果
//杂加?BufferedReader
BufferedReader br = new BufferedReader(fr);
//4.字节的缓冲数组 是字节
//字符的缓冲数组 是字符
char[] cbuf = new char[4];//缓冲区是4个字符
//这个方法是用的!!!
//5. read(char[] cbuf) 将文件内容读取到字符数组中(缓冲区)
int length = -1;

while ((length = br.read(cbuf)) != -1) {
System.out.println(length);
//6.将数组展示到控制台
System.out.println(new String(cbuf, 0, length));
}

//7.关闭流
br.close();
fr.close();

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.qfedu.d_FileReader;

import java.io.*;

public class Demo2 {
public static void main(String[] args) throws IOException {
//将c盘下面的aaa文件夹下面的1.txt文件的内容 读取到Java内存中
//和字节流一模一样
//1.新建一个File对象
File file = new File("c:/aaa/1.txt");
//2.新建FileReader 对象 字符输入流
FileReader fr = new FileReader(file);
//3.FileRFeader没有缓冲的效果,可以加上缓冲的效果
//杂加?BufferedReader
BufferedReader br = new BufferedReader(fr);

//这个方法不用!!!
//5.int read() 一次读取一个字符,返回值是字符对应的assic码值
int length = -1;
while ((length = br.read()) != -1) {
System.out.println(length);

}

//7.关闭流
br.close();
fr.close();

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.qfedu.d_FileReader;

import java.io.*;

public class Demo3 {
public static void main(String[] args) throws IOException {
//将c盘下面的aaa文件夹下面的1.txt文件的内容 读取到Java内存中
//和字节流一模一样
//1.新建一个File对象
File file = new File("c:/aaa/1.txt");
//2.新建FileReader 对象 字符输入流
FileReader fr = new FileReader(file);
//3.FileRFeader没有缓冲的效果,可以加上缓冲的效果
//杂加?BufferedReader
BufferedReader br = new BufferedReader(fr);
//4.字节的缓冲数组 是字节
//字符的缓冲数组 是字符
char[] cbuf = new char[4];//缓冲区是4个字符
//这个也不用!!!
//5. read(char[] cbuf, 0, length) 将文件内容读取到字符数组中(缓冲区)
int length = -1;
while ((length = br.read(cbuf, 0, 2)) != -1) {
System.out.println(length);
//6.将数组展示到控制台
System.out.println(new String(cbuf, 0, length));
}

//7.关闭流
br.close();
fr.close();

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.qfedu.d_FileReader;

import java.io.*;

public class Demo4 {
public static void main(String[] args) throws IOException {
//将c盘下面的aaa文件夹下面的1.txt文件的内容 读取到Java内存中
//和字节流一模一样
//1.新建一个File对象
File file = new File("c:/aaa/1.txt");
//2.新建FileReader 对象 字符输入流
FileReader fr = new FileReader(file);
//3.FileRFeader没有缓冲的效果,可以加上缓冲的效果
//杂加?BufferedReader
BufferedReader br = new BufferedReader(fr);
//一次读取一行数据,r如果到达行的末尾就返回就是一个null
// String s = br.readLine();
// System.out.println(s);
// String s1 = br.readLine();
// System.out.println(s1);
String str;
//有可能会用的
while ((str = br.readLine()) != null) {
System.out.println(str);
}


//7.关闭流
br.close();
fr.close();

}
}

1.2.4字符输出流

案例:

​ 将一个字符串类型的数据写入到磁盘中

FileWriter

写字符文件一个便利类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.qfedu.e_fileWriter;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class Demo1 {
public static void main(String[] args) throws IOException {
//从内存写入数据到磁盘的文件中
File file = new File("c:/aaa/2.txt");
FileWriter fw = new FileWriter(file);
//加缓冲流
BufferedWriter bw = new BufferedWriter(fw);
//viod writer(char[] cbuf, int off, int length);
String str = "今天的天气真的太热了";
//将字符串转为char数组
char[] chars = str.toCharArray();
//将chars数组中 从0 开始 取3个字符写入到缓冲流中
bw.write(chars, 3, 3);
bw.close();
fw.close();


}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.qfedu.e_fileWriter;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class Demo2 {
public static void main(String[] args) throws IOException {
//从内存写入数据到磁盘的文件中
File file = new File("c:/aaa/2.txt");
FileWriter fw = new FileWriter(file);
//加缓冲流
BufferedWriter bw = new BufferedWriter(fw);
//viod writer(char[] cbuf);
String str = "今天的天气真的太热了";
//将字符串转为char数组
char[] chars = str.toCharArray();
//将chars数组中,直接将字符数组中的所有的数据写到缓冲流中
bw.write(chars);
bw.close();
fw.close();


}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.qfedu.e_fileWriter;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class Demo3 {
public static void main(String[] args) throws IOException {
//从内存写入数据到磁盘的文件中
File file = new File("c:/aaa/2.txt");
FileWriter fw = new FileWriter(file);
//加缓冲流
BufferedWriter bw = new BufferedWriter(fw);
//viod writer(String str);

String str = "对方网络不佳!!!";
//viod writer(String str, int off, int length);
//bw.write(str);
//bw.write(str, 1, 4);
bw.write("呵呵");
bw.newLine();//写完呵呵以后换行写 功能代码 起到一个换行的作用
bw.write("哈哈");
bw.newLine();
bw.write("嘻嘻");
bw.close();
fw.close();


}
}

综合案例:

​ 复制一本小说到另外一个盘符下面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.qfedu.e_fileWriter;

import java.io.*;

public class Demo4 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(new File("c:/bbb/DiBa.txt")));
BufferedWriter bw = new BufferedWriter(new FileWriter(new File("c:/aaa/sb1.txt")));

int length = -1;
char[] cbuf = new char[4 * 1024];
while ((length = br.read(cbuf)) != -1) {
bw.write(cbuf, 0, length);

}
bw.close();
br.close();

}
}

复制一个图片到另外一个盘符不能使用字符流,因为解码,编码的时候会出问题的!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.qfedu.e_fileWriter;

import java.io.*;

public class Demo5 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(new File("c:/aaa/3.gif")));
BufferedWriter bw = new BufferedWriter(new FileWriter(new File("c:/bbb/333.gif")));

int length = -1;
char[] cbuf = new char[4 * 1024];
while ((length = br.read(cbuf)) != -1) {
bw.write(cbuf, 0, length);

}
bw.close();
br.close();

}
}

总结:

1
2
3
4
字节流:
可以处理 文本 图片 音频 视频
字符流:
只能处理文本

day21

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
1.为什么重写equals方法必须重写hashCode方法
是因为判断两个对象是否相等的时候,先判断hash值是否相等,然后再比较内容
hash一样的对象不一定相等,但是对象相等,要求hash值必须一样的
2.使用字符流复制一个文本文档到另外一个盘符下面
BufferedReader br = new BufferedReader(new FileReader(new File("路径")));
BuffererWriter bw = new BufferedWriter(new FileWriter(new File("被复制到哪的路径"));
int length = -1;
char[] cbuf = new char[4 * 1024];
while ((length = br.read(cbuf))) {
bw.write(cbuf, 0, length);
}
bw.close();
br.close();
3.接口和抽象类的区别
接口的定义:
interface 接口 {
抽象的方法

抽象类:
abstract 抽象类 {
非抽象方法
抽象方法

用一个普通类去继承(extends)抽象类
用一个普通类去实现(inplements)接口

4.重写和重载的区别
重写:
有继承关系,在子类中去重写父类的方法
方法完全一样的,方法的额名字 返回值 参数
重载:
只能在一个类中,方法名字一样,参数列表一定不同
5.final关键字的使用
final修饰成员变量
修饰局部变量
修饰方法
修饰类
面试:为啥使用final修饰成员变量?这个final修饰的量不能再赋值,值不能再改变了
效率就会高点!!!
6.static关键字的使用
static修饰成员变量
修饰成员方法
修饰代码块
类名.成员变量
类名.方法
7.复制一个视频文件到另外一个盘符下面 使用字节流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File ("文件路径")));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("被复制的路径")));
byte[] buf = new byte[4 * 1024];
int length = -1;
while ((length = bis.read(buf))) {
bos.write(buf, 0, length);
}
bos.close()
bis.close();

关于流,你只要听懂昨天讲的流,那么其他流都在不再话下,思路都是一样的!!!

教你们方法 方式,而不是单纯的教技术!!! 这些东西都是可以举一反三的!!!

回顾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.IO流
干嘛的?对磁盘上面的文件的内容进行 读和写的
2.
输入流:
对磁盘上面的文件进行读取的 磁盘-》内存的
FileInputStream 字节输入流 想加缓冲效果 BufferedInputStream
这个类一个方法 read()
FileReader 字符输入流 想加缓冲效果 BufferedReader
这个类一个方法 read()
输出流:
从内存中写入到磁盘 内存-》磁盘
FileOutputStream 字节输出流 想加缓冲效果 BufferedOutputStream
这个类一个方法 write()
FileWriter 字符输出流 想加缓冲效果 BufferedWriter
这个类一个方法 write()



之后再讲一个对象流 序列化!!!

我不用讲,你看着官方手册就会写了

今天的内容

常用类

1.常用类

1.1StringBuffer类

看API官方手册

线程安全的,可变的字符序列

关于StringBuffer的代码相当简单是个人都会的,但是底层和面试问的东西比较深的所以你们下去一定看看一些东西

String 不可变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.qfedu.a_stringbuffer;

public class Demo1 {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer();
//构造一个没有字符的字符串缓冲区(容器),初始容量为16个字符。
//在整个缓冲去放数据,放啥数据?字符串!!!
System.out.println(sb);
sb.append("xi");//追加
sb.append("he");
sb.append("ha");
//插入 这个2 就是下标索引
sb.insert(2, "gou");
System.out.println(sb);//xigouxihehehaha
//字符串反转
System.out.println(sb.reverse());//ahahehehixuogix
//删除一个字符串delet(int start, int end)
//不要怀疑 始终记得 要头不要尾 start包含 end 不包含
System.out.println(sb.delete(1,3));

//问题:
//咱们有String了为啥还要学习StringBuffer和StringBuilder
//拼接字符串的效率不一样的
//字符串String 拼接的是时候,效率底 + concat()
//StringBuffer 和StringBuilder拼接字符串效率高
//为啥StringBuffer和StringBuilder高,为啥String效率低
//如果使用String拼接100个字符串,会出来100个对象 因为String 不可变的
String str = "123";
str += "457";
str += "457";
str += "457";
str += "457";
//以上有几个对象? 6个


}
}

作业:自己查StringBuilder这个类,然后写方法

面试题:String Stringbuffer StringBuilder区别 下去一定好好看看

1
https://blog.csdn.net/pf6668/article/details/108875324
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.qfedu.a_stringbuffer;

public class Test {

public static void main(String[] args) {
//String 连接10000次消耗1127ms
//StringBuffer 连接10000次消耗5ms
//StringBuilder 连接10000次消耗3ms
StringTest(10000);
StringBufferTest(10000);
StringBuilderTest(10000);
}
public static void StringTest(int n){
String str = "";
//获取当前系统的时间 还没有执行下面for循环的时候的时间
Long startTime = System.currentTimeMillis();
//使用for循环来拼接!!!
for(int i=0;i<n;i++){
str += i;
}
//获取当前系统的时间 执行下面for循环的时候的时间
Long endTime = System.currentTimeMillis();
System.out.println("String 连接"+ n +"次消耗"+(endTime-startTime)+"ms");
}
public static void StringBufferTest(int n){
StringBuffer str = new StringBuffer();
Long startTime = System.currentTimeMillis();
for(int i=0;i<n;i++){
str.append(i);
}
Long endTime = System.currentTimeMillis();
System.out.println("StringBuffer 连接"+ n +"次消耗"+(endTime-startTime)+"ms");
}
public static void StringBuilderTest(int n){
StringBuilder str = new StringBuilder();
Long startTime = System.currentTimeMillis();
for(int i=0;i<n;i++){
str.append(i);
}
Long endTime = System.currentTimeMillis();
System.out.println("StringBuilder 连接"+ n +"次消耗"+(endTime-startTime)+"ms");
}
}

我们可以清楚的看到String的字符串的连接效率是最低的,这一点对于大量字符串的拼接可以很明显的表示出来,所以说大量字符串的拼接最好不要选择String。StringBuffer和StringBuilder对于字符串的拼接效率是大致相同的

为啥StringBUilder效率高?因为StringBuilder是线程不安全的,StringBuffer线程安全

四、总结
1.String为固定长度的字符串,StringBuilder和StringBuffer为变长字符串;
2.stringBuffer是线程安全的,StringBuilder是非线程安全的;
3.StringBuffer和StringBuilder的默认初始容量是16,可以提前预估好字符串的长度,进一步减少扩容带来的额外开销

1.2枚举类(enum)

1
2
3
4
Java 中的枚举是一个特殊的类,一般是表示一组常量  final  static int a = 30;
比如一年有四季 一天24小时 pi e

枚举就是用来表示常量的,只不过是常量的换一种写法而已!!!

Java中定义枚举类的时候的语法格式:

1
2
3
4
public enum 枚举类名 {
//下面写常量,常量之间使用逗号隔开

}
1
2
3
4
5
6
7
package com.qfedu.b_enum;

public enum Color {
//常量,一般都是大写的 没有任何数据类型的!!!
RED, GREEN, BLUE
}

枚举类的实例用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.qfedu.b_enum;

//声明一个枚举类 是一个特殊的类
enum Color2 {
RED, GREEN, BLUE
}
public class Demo2 {
public static void main(String[] args) {
//RED这个数据 属于Color2这个类的实例的
//Color2.RED 为啥可以用类名.常量 常量是static修饰的!!!
Color2 red = Color2.RED;
System.out.println(red);//RED
Color2 green = Color2.GREEN;
System.out.println(green);

}
}

swicth (表达式) {}

1
2
表达式数据类型有哪些:
byte short int char String 枚举

枚举在swicth-case中如何使用【这个重要】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.qfedu.b_enum;

enum Color3 {
RED, GREEN, BLUE
}
//可以通过switch-case将咱们的常量变成咱们所数据类型
//RED===》红色
//只是一个用判断的一个东西
public class Demo3 {
public static void main(String[] args) {
Color3 red = Color3.RED;
switch (red) {
case RED:
System.out.println("红色");
break;
case GREEN:
System.out.println("绿色");
break;
case BLUE:
System.out.println("蓝色");
break;
}
}
}

枚举类中的几个方法

1
2
3
values();枚举类中所有的值
ordinal();可以找到每个枚举类中常量的索引
valueOf();返回值的是指定字符串的枚举常量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.qfedu.b_enum;

enum Color4 {

RED, GREEN, BLUE
}
public class Demo4 {
public static void main(String[] args) {
//values();枚举类中所有的值
//ordinal();可以找到每个枚举类中常量的索引
//valueOf();返回值的是指定字符串的枚举常量
Color4[] values = Color4.values();//返回是数组
//遍历
for (Color4 value : values) {
System.out.println(value + "对应的索引:" + value.ordinal());
}
//valueOf返回的是字符串的枚举常量对象
//只是换了一种写法而已。
//通过常量所对应的字符串获取常量的对象
Color4 red = Color4.valueOf("RED");
//一般开发使用Color4.RED;
Color4 red1 = Color4.RED;
System.out.println(red);
System.out.println(red.hashCode());
System.out.println(red1);
System.out.println(red1.hashCode());
}
}

枚举在真实的开发中使用:

1
2
3
在很多时候我们定义一些状态量都是使用0或者1,-1这样在数据库中定义,这样的数据存放在数据库的相应字段中方便数据读取,但是只存储数字,如果定义的表很多,对应的字段也很多,设计的状态就非常多,编码很容易分辨不清楚,一方面去数据库中查看相应字段的注释非常费时间,而且容易产生差错,如果我们使用枚举类,这个问题就很好的解决了。

比如当我们定义了许多状态量,比如订餐支付,未支付,订单的完结,派送,制作等有许多状态,如果不使用枚举,我们在代码层进行判断的时候总是要去思考这个状态是定义的什么呢,0还是-1呢,当项目比较大的时候,光靠我们记是不行的,干脆我们就使用枚举来简化我们的编程,而且还较少了逻辑上出错的可能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.qfedu.b_enum;


enum ProductEnum {
UP(0, "在架"),
DOWN(1, "下架");
private Integer code;
private String message;
ProductEnum(Integer code, String message) {
this.code = code;
this.message = message;
}

public Integer getCode() {
return code;
}

public void setCode(Integer code) {
this.code = code;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}
}
public class Demo5 {
public static void main(String[] args) {
System.out.println(ProductEnum.UP.getCode());
System.out.println(ProductEnum.UP.getMessage());
System.out.println(ProductEnum.DOWN.getCode());//1
System.out.println(ProductEnum.DOWN.getMessage());//"下架"
}
}

1.3包装类

Java有八大基本数据类型,都有与之对应的包装类

为啥会有八大基本数据类型的包装类?

包装类能被实例化,封装了很多方法,可以使用,这样操作八大基本数据类型就比较方便了

所以开发中的实体类都是写的包装类

int age====>Integer age;

int->Integer

byte->Byte

short->Short

long->Long

float->Float

double->Double

boolean->Boolean

char->Character

【重点】:

​ 1.自从JDK1.5之后,有拆箱和装箱

​ 自动装箱:将基本数据类型转为包装类

​ 自动拆箱:将包装类转为基本数据类型

​ 2.***Value();将包装类转为基本数据类型

​ 3.toString();将基本数据类型转为字符串

​ 4.parse***();将字符串类型的数据转为基本数据类型【以后开发用】

​ 5.valueOf();将基本数据类型转为包装类类似手动装箱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.qfedu.c_baozhuang;

public class Demo1 {
public static void main(String[] args) {
//自1.5以后 有自动装箱和自动拆箱
//自动装箱: 将int转为Integer
//自动拆箱: 将Integer转为int
//毫无违和感 都是自动的!!!特别方便
System.out.println(Integer.MAX_VALUE);//2147483647
System.out.println(Integer.MIN_VALUE);//-2147483648

//自动装箱 45本身是int类型的数据,现在是Integer
//int 变成了Integer 报错没?没有就证明可以自动的装箱

Integer i1 = 45;
//自动拆箱 没有报错 就证明自动的在拆箱
int i2 = i1;
System.out.println(i1);
System.out.println(i2);
//除了自动装箱和拆箱外,Java还给咱们提供了方法。让咱们装箱和拆箱

Boolean b1 = true;//b1 是包装类
//***Value();将包装类转为基本数据类型
//intValue() booleanValue() floatValue() doubleValue()
//b是基本数据类型
//有自动拆箱,还有必要使用***Value吗?没有!!!
boolean b = b1.booleanValue();
System.out.println(b);

//toString();将基本数据类型转为字符串【重要】
int i3 = 45;
String s = Integer.toString(i3);
System.out.println(s);//"45"
String s1 = Double.toString(67.8);
System.out.println(s1);//"67.8"
//以后做银行项目的时候,double 和float 有可能数据丢失
//可以转为字符串操作!!!绝对不会
//parse***();将字符串类型的数据转为基本数据类型【以后开发用】
//parsetInt(); parseDouble(); parseFloat();parseByte()
int i = Integer.parseInt("250");
System.out.println(i);//250
//开发中 分页 前段URL传一个字符串值过来 pageNo=2 这个2传
//到Java代码中,是字符串类型的数据,将字符串转为int类型的数据
//然后操作数据库

//valueOf();将基本数据类型转为包装类类似手动装箱
byte b2 = 34;//b2 基本数据类型
Byte aByte = Byte.valueOf(b2);
System.out.println(aByte);//aByte 就是包装类
//有没有必要写这个方法?没有 因为会自动装箱


}
}

面试题:

1
2
3
4
5
6
Integer i1 = 10;
Integer i2 = 10;
sout(i1 == i2);
Integer i3 = 1000;
Integer i4 = 1000;
sout(i3 == i4);
关于Integer和int类型数据的内存分析

只有看内存分析才能明白一些底层的东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];//缓存数组 -128~127

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;

cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);

// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}

/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.qfedu.c_baozhuang;

public class Demo3 {
public static void main(String[] args) {
Integer i1 = 10;//没有new 直接从常量池中取的
Integer i2 = 10;//没有new 直接从常量池中取的
System.out.println(i1 == i2);//true
Integer i3 = 1000;//new对象了
Integer i4 = 1000;//new对象了
System.out.println(i3 == i4);//false

//考察了Integer类加载的时候,
// 它就创建了自己的静态空间(常量池),
// 立即加载了Integer类型的数组, static final Integer cache[];
// 数组内存储了256个Integer类型的对象-128 - 127,
// 如果我们用的对象范围在这之内,是final修饰的,就意味着不会改变地址
//i1 没有超过-128 ~127这个范围的时候,不会改变地址的,就意味着内存地址是一样的
//使用==的时候,比较内存地址,所以相等!!!
// 如Ingeger i1 = 10;JVM就会直接从静态区的Integer类型的缓冲数组中直接找对应的对象,
// 如果我们用的对象范围超出了这个-128~127,例如Integer i1 = 1000;
// JVM就会帮我们在堆内存中创建一个新的Integer对象。那么内存地址不一样的
// 使用==比较的就是一个false

}
}

1

京东面试题:

1
2
3
4
5
6
7
8
Integer i1 = 10;
Intger i2 = 10;
sout(i1 == i2);//true 不会创建新的对象,直接从常量池
Integer i3 = 1000;
Integer i4 = 1000;
sout(i3 == i4);//false 已经超过缓存数组的容量了-128~127 ,就要在堆区去创建新的对象了


总结:

int类型赋值:不管值的大小,都在常量池中!!!

1
int a=  128;  int a = 8999;   都在常量池中!!!

Integer类型赋值:

1
2
3
4
Integer i1 = 10;//不会在堆区创建对象
Integer i2 = 1000;//堆区会创建对象,值会指向常量池中的那个值!!!
若值的范围在-128~127之间,在常量池中!!!
如值的范围不在-128~127之间,在堆区创建对象,进行赋值了

实例化Integer对象:

1
2
Integer i1 = new Integer(12);
只要是new的Integer 使用== 都是false

今天这些东西其实是你自己要私下搞的!!!

因为那么类,包装类特别多每个都要讲?需要咱们后期慢慢的消化的东西!!!

面了那么家只有京东问到了,出现的概率小,但是咱们很有必要去了解的!!!

自己去百度一下,你就知道了

1.4Math

Math类包含执行基本数字运算的方法,如基本指数,对数,平方根和三角函数。

专门处理数学运算的一个类

绝对值 abs()

最大值 max()

最小值 min()

向上取整 ceil()

向下取整floor()

四舍五入round()

随机数random()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.qfedu.d_math;

public class Demo1 {
public static void main(String[] args) {
System.out.println(Math.PI);
System.out.println(Math.E);
//以上是两个常量
System.out.println(Math.abs(-89));//绝对值 89
System.out.println(Math.max(34, 56));//最大值 56
//求三个数的最大值?咋办?
System.out.println(Math.max(23, Math.max(45,12)));//45

//最小值
System.out.println(Math.min(34, 12));//12
//向上取整
System.out.println(Math.ceil(45.2));//46.0
//向下取整
System.out.println(Math.floor(89.8));//89.0
//四舍五入
System.out.println(Math.round(45.5));//46
//随机数大于等于 0.0 ,小于 1.0 double
System.out.println(Math.random());
//随机1-100之间的整数int 0.45 * 100=45 int
int i1 = (int)(Math.random() * 100);
System.out.println(i1);


}
}

1.5Random类

专门处理随机数的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.qfedu.e_random;

import java.util.Random;

public class Demo1 {
public static void main(String[] args) {
Random random = new Random();
System.out.println(random.nextInt());
System.out.println(random.nextInt(100));//[0,100);
System.out.println(random.nextFloat());
System.out.println(random.nextDouble());
System.out.println(random.nextBoolean());



}
}

作业:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1.题目要求:

根据int变量n的值,来获取随机数字,范围是[1 , n],可以取到1也可以取到n。
2.题目:

用代码模拟猜数字的小游戏。

思路:

1.首先需要产生一个随机数字,并且一旦产生不再变化。用Random的nextInt方法

2.需要键盘输八,所以用到了Scanner

3.获取键盘输入的数字,用scanner当中的nextInt方法

4.已经得到了两个数字,判断(if)一下:

如果太大了,提示太大,并且重试;

如果太小了,提示太小,并且重试;

如果猜中了,游戏结束。

5.重试就是再来一次,循环次数不确定,用while(true)。


1.6System类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.qfedu.f_system;

import java.io.PrintStream;
import java.util.Properties;
import java.util.Scanner;

public class Demo1 {
public static void main(String[] args) {

PrintStream out = System.out;//是一个流 PrintStream 打印流
out.println("goudan");
System.out.println("goudan");//标准输出流打印的东西
System.err.println("xixi");//错误输出流打印的东西
new Scanner(System.in);//输入流
//你把控制台想象成一个磁盘。就是输入流和输出流
//用的很多
// 在1970年1月1日UTC之间的当前时间和午夜之间的差异,以毫秒为单位
long l = System.currentTimeMillis();
//2022年到1970年 是不是52
System.out.println(l/1000/60/60/24/365);

//获取系统的东西 System 英文单词就是系统的意思
Properties properties = System.getProperties();
//os.name 系统的名字
System.out.println(properties.get("os.name"));
System.out.println(properties.get("os.version"));
System.out.println(properties.get("user.name"));
System.out.println(properties.get("user.dir"));
System.out.println(properties.get("java.version"));
//Windows 10
//10.0
//bowang
//C:\Users\bowang\IdeaProjects\day21_wb
//1.8.0_241



}
}

1.7Runtime【不重要】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.qfedu.g_runtime;

import java.io.IOException;

public class Demo1 {
public static void main(String[] args) throws IOException {
//1.获取runtime对象
Runtime runtime = Runtime.getRuntime();
//runtime对象可以启动咱们电脑上面任意的一个软件
//使用代码启动软件
runtime.exec("C:\\Program Files (x86)\\Notepad++\\notepad++.exe");
//返回Java虚拟机将尝试使用的最大内存量。字节数
System.out.println(runtime.maxMemory() / 1024 / 1024);//MB
//返回Java虚拟机中的可用内存量 字节数
System.out.println(runtime.freeMemory()/1024/1024);
//返回Java虚拟机中的内存总量 字节数
System.out.println(runtime.totalMemory()/1024/1024);
}
}

1.8Date类

专门处理日期的一个类,不过好多方法已经过期了

在类的所有方法Date接受或返回年,月,日,小时,分钟和秒值,以下表述中使用:

  • y年代表整数y - 1900
  • 一个月由0到11的整数表示; 0是1月,1是2月,等等; 11月12日。
  • 日期(月的一天)以通常的方式从1到31的整数表示。
  • 一小时由0到23之间的整数表示。因此,从午夜到凌晨1点的时间是小时0,从中午到下午1点的小时是12小时。
  • 一般以0〜59的整数表示。
  • 第二个由0到61的整数表示; 值60和61仅发生在闰秒上,甚至仅在实际上正确跟踪闰秒的Java实现中发生。 由于目前引入闰秒的方式,在同一分钟内不会发生两个闰秒,但是本规范遵循ISO C的日期和时间约定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.qfedu.h_date;

import java.util.Date;

public class Demo1 {
public static void main(String[] args) {
Date date = new Date();
//Tue Aug 09 16:11:11 IRKT 2022
System.out.println(date);
//获取年份 这个年份是减去1900以后的值,所以得加上1900
System.out.println(date.getYear() + 1900);
//7
//2
//16
//15
//42
//获取月份
System.out.println(date.getMonth() + 1);//7
//这个日期的星期几。
System.out.println(date.getDay());//2
System.out.println(date.getHours());//16
System.out.println(date.getMinutes());//15
System.out.println(date.getSeconds());//42


}
}

1.9Calendar

现在国际通用的是Calendar类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.qfedu.h_date;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class Demo2 {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
//获取当前的年份
int i = calendar.get(Calendar.YEAR);
System.out.println(i);//2022
//获取月份 需要加1的
System.out.println(calendar.get(Calendar.MONTH) + 1);//8
//获取当月的第几天
System.out.println(calendar.get(Calendar.DAY_OF_MONTH));//9
//获取这周的第几天 漂亮国的周日是第1天
System.out.println(calendar.get(Calendar.DAY_OF_WEEK));//3
//获取这一年的第几天
System.out.println(calendar.get(Calendar.DAY_OF_YEAR));//221
//获取时
System.out.println(calendar.get(Calendar.HOUR));//4
System.out.println(calendar.get(Calendar.MINUTE));//39
System.out.println(calendar.get(Calendar.SECOND));//3

//重要的方法 获取当前的时间的 返回值是Date类型的数据
//可以转为Date类型的数据
Date time = calendar.getTime();
System.out.println(time);
//Tue Aug 09 16:40:21 IRKT 2022
//这上面时间 对程序员相当不友好!!!
//通过一个类将日期格式转换一下
SimpleDateFormat sdf = new SimpleDateFormat("yyyy—MM-dd HH:mm:ss");
//yyyy-MM-dd HH:mm:ss
//年-月-天 时:分:秒
//2022-08-09 16:43:32
//将这个格式Tue Aug 09 16:40:21 IRKT 2022
//转换为2022-08-09 16:43:32
String format = sdf.format(time);
System.out.println(format);
//yyyy-MM-dd HH:mm:ss
//以上能不能胡乱写?不能
//以后开发中要用的!!

}
}

输入两个日期计算出两个日期相差多少天

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.qfedu.h_date;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Demo3 {
public static void main(String[] args) throws ParseException {

//输入两个日期计算出两个日期相差多少天
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//将一个字符串类型的数据变成一个Date类型的数据
Date strat = sdf.parse("2012-08-01");
Date end = sdf.parse("2022-08-09");
//一定要将字符串转为Date类型 才能按照日期来算
//将Date类型的数据转为时间戳了,
//为啥转为时间戳?因为时间戳是一个long类型数据可以想减!!!
//算两个时间的间隔,相减。转为int或者long才能转
long startTime = strat.getTime();
long endTime = end.getTime();//毫秒数
long num = endTime - startTime;
System.out.println(num/1000/60/60/24);
}
}

作业:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
1.输入两个日期计算出两个日期相差多少天
2.一个你的生日,计算出来你几岁了
3.新闻类:标题 内容 时间
有10条新闻 ,按照时间降序进行排列 打印!!!

4.学生类:学号 姓名 出生日期
把学生类放到集合中,
找出来比张三年龄大的学生
5.题目要求:

根据int变量n的值,来获取随机数字,范围是[1 , n],可以取到1也可以取到n。
6.题目:

用代码模拟猜数字的小游戏。

思路:

1.首先需要产生一个随机数字,并且一旦产生不再变化。用Random的nextInt方法

2.需要键盘输八,所以用到了Scanner

3.获取键盘输入的数字,用scanner当中的nextInt方法

4.已经得到了两个数字,判断(if)一下:

如果太大了,提示太大,并且重试;

如果太小了,提示太小,并且重试;

如果猜中了,游戏结束。

5.重试就是再来一次,循环次数不确定,用while(true)。
7.自己去百度一下Calendar这个类详解。
因为以后开发中日期在项目中是必用的!!!啥时候下的单子,超过15分钟没有支付。都要考虑的!!!日期很重要的!!!

day22

今天的内容【重要!!!】

1.进程

2.线程【重点】

1.什么是进程

是独立的运行程序

​ 比如咱们电脑软件,你启动起来以后,他就是一个进程。qq idea

进程需要windows系统的分配。可以获取当前的系统的网卡,内存,显卡等

​ 1.独立性

​ 各个进程之间是相互的独立的互不影响 的。录屏软件和idea没有关系的

​ 2.互斥性

​ 每个软件系统都会分配一个独立端口号,如果启动一个软件以后他的端口号97。

​ 如果再启动另外一个软件,另外一个软件如果端口也是97,这个程序就启动不了,端口被占用的

脑海里面知道开启的软件就是一个进程 即可!!!

2.什么是线程

进程是由多个或者一个线程组成的。每个进程至少得有一个线程的支撑。

脑海里面这样来想,一个进程(qq),qq里面很多个线程在执行。线程的执行

支撑起来了进程的执行。

把一个人比作一个进程,那么你身体里面的细胞就是线程。如果没有细胞。这个人还存在吗?不存在的!!!

进程包含了线程,线程是组成进程的最小基本单位

​ 特性:

​ 1.抢占式运行的【重要】

​ CPU在执行的时候,按照时间片来执行的,单位的时间片是抢占是执行

​ 比如 idea qq 抢占CPU的,比如qq的线程抢到cpu,idea线程等待

​ 我是一个cpu。你们问我问题。75个线程。同时来问我问题吗?不是

​ 抢着问。一个问。然后其他人等待。这个人甚至还没有问完,其他的某一个人

​ 抢到我了,他问我。大概就是效果

​ 2.资源共享性

​ 一个线程可以共享当前CPU, 网卡等

Java程序:

​ 一个Java程序就是一个进程 Demo1 就是一个应用程序 就是一个进程

​ 一个Java程序Demo1里面至少 几个线程?

​ 两个:

​ main主函数线程

​ JVM垃圾回收器线程

3.线程和进程的区别【面试题】

1
2
3
4
5
6
进程是一个应用程序,是独立的
线程是进程中最小的基本单位。
把进程比作生产车间,每个流水线就是一个线程
进程有独立性和互斥性
线程有抢占式资源共享特性

4.并发和并行

并发:同时发生,轮流交替执行

并行:真正意义的同时执行

比如:

​ 你去饭店点了两个菜,生活中拿个筷子轮流夹菜哦这就是并发场景

​ 端起盘子,同时倒到嘴里面,这就是并行场景

5.创建线程的两种方式【重点】

创建线程的两种方式

  1. 一个是将一个类声明为Thread的子类。 这个子类应该重写run方法 。 然后可以分配并启动子类的实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.qfedu.a_thread;

// 一个是将一个类声明为Thread的子类。
// 这个子类应该重写run类的方法Thread 。
// 然后可以分配并启动子类的实例。
class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("我是myThread1线程:" + i);
}
}
}
class MyThread2 extends Thread {
@Override
public void run() {//run方法中写功能代码 就是一个线程中执行的一个功能
for (int i = 0; i < 500; i++) {
System.out.println("我是mythread2线程:" + i);
}
}
}
public class Demo1 {
public static void main(String[] args) {
//官方手册中说,要去实例化Thread的子类,并启动线程
MyThread1 myThread1 = new MyThread1();
//启动线程 使用start方法 在主线程中开启子线程
myThread1.start();
MyThread2 myThread2 = new MyThread2();
myThread2.start();
//现在有几个线程? 3个
//一个是MyThread1线程 一个是主线程(main) 一个垃圾回收机制线程
for (int i = 0; i < 500; i++) {
System.out.println("主函数线程:" + i);
}
//发现先执行了主线程,然后再执行子线程,然后又执行主线程
//这就线程的抢占式的运行
//三个线程:
//你自己吃三盘菜,咋吃?
//一盘菜代表一个线程,一盘菜夹一下,随机的吧。
//开启一个线程,就是在执行一个任务。
//上面这个代码,你们执行结果和我执行的结果一样吗?绝对不一样的
//抢占式的,随机执行线程的!!!

}
}
练习:
main主线程 打印100遍的吃大盘鸡
子线程1 打印100遍的吃水煮肉片
子线程2 打印100遍的吃毛血旺
一定要注意打印的结果,多执行几遍,看看每次执行的结果是否一样!!!

另一种方法来创建一个线程是声明实现类Runnable接口。 那个类然后实现了run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.qfedu.a_thread;

//另一种方法来创建一个线程是声明实现类Runnable接口。
// 那个类然后实现了run方法。
// 然后可以分配类的实例(创建类的对象),在创建Thread实例时作为参数传递,并启动。
class MyThread3 implements Runnable {


@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("MyThread3:" + i);
}
}
}
class MyaThread4 implements Runnable {

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("MyThread4:" + i);
}
}
}
public class Demo2 {
public static void main(String[] args) {
MyThread3 myThread3 = new MyThread3();
//Thread(Runnable target) 参数是Runnable这个接口对象
//分配一个新的 Thread对象。
Thread thread = new Thread(myThread3);
thread.start();
MyaThread4 myaThread4 = new MyaThread4();
Thread thread1 = new Thread(myaThread4);
thread1.start();
for (int i = 0; i < 100; i++) {
System.out.println("main主线程:" + i);
}
//有几个线程做任务的线程 3个 一个是main主线程 一个是MyThread3
//一个MyThread4这个线程
//可以发现运行的结果是随机执行的!!!

}
}

6.线程下面的几个方法

构造方法

Thread()分配一个新的 Thread对象。 无参构造方法
Thread(Runnable target)分配一个新的 Thread对象。 有参构造
Thread(Runnable target, String name)分配一个新的 Thread对象。并起名字

线程方法:

static Thread currentThread()返回对当前正在执行的线程对象的引用
String getName()返回此线程的名称。
void setName(String name)将此线程的名称更改为等于参数 name
int getPriority()返回此线程的优先级。
void setPriority(int newPriority)更改此线程的优先级。
设置优先并不一定优先,只是增加了执行的概率。最小值是1,最大值是10,默认的是5
static void sleep(long millis)使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.qfedu.b_threadfun;

class MyThread1 implements Runnable {
@Override
public void run() {
Thread thread = Thread.currentThread();
//此时这个thread对象是MyThread1这个线程
//对子线程设置名字
thread.setName("mythread1子线程");
System.out.println(thread.getName());//Thread-0
}
}
class MyThread2 implements Runnable {
@Override
public void run() {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());//狗蛋
}
}
public class Demo1 {
public static void main(String[] args) {
//currentThread() 获取当前线程对象
Thread thread = Thread.currentThread();
//现在thread 这个对象是哪个线程?主线程
//为啥是main主线程
//给main主线程设置名字
thread.setName("主线程");
//获取的是main主线程的名字,Jvm会给主线程还有其他线程一个默认的名字

System.out.println(thread.getName());//main
// MyThread1 myThread1 = new MyThread1();
// Thread thread1 = new Thread( myThread1);
// thread1.start();

new Thread(new MyThread1()).start();
//Java中默认的主线程叫main 子线程叫Thread-0 Thread-1....
//默认的有名字,咱们能不能对线程自定义名字?能

//我在启动MyThread2的这个线程的时候顺便起名字
MyThread2 myThread2 = new MyThread2();
//myThread2线程的名字就叫狗蛋,是在创建线程的时候就已经起好名字了
Thread thread1 = new Thread(myThread2, "狗蛋");
thread1.start();


}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.qfedu.b_threadfun;

class MyThread3 implements Runnable {
@Override
public void run() {
//想看一下MyThread3这个线程的优先级是几
Thread thread = Thread.currentThread();
thread.setPriority(10);
System.out.println(thread.getPriority());
for (int i = 0; i < 100; i++) {
System.out.println("MyThread3线程:" + i);
}
}
}
class MyThread4 implements Runnable {
@Override
public void run() {
//想看一下MyThread3这个线程的优先级是几
Thread thread = Thread.currentThread();
thread.setPriority(1);
System.out.println(thread.getPriority());
for (int i = 0; i < 100; i++) {
System.out.println("MyThread4线程:" + i);
}
}
}
public class Demo2 {
public static void main(String[] args) {

//默认的优先级都是5,能不能手动去修改某一个线程的优先级?
//可以
Thread thread = Thread.currentThread();
//主线程的优先级设置1
//thread.setPriority(1);
//获取主线程的优先级
System.out.println(thread.getPriority());//5
//优先级 1 ~10 1的优先级最低 10的优先级最高
//jvm默认线程的优先级是5
// for (int i = 0; i < 100; i++) {
// System.out.println("主线程:" + i);
// }
new Thread(new MyThread3()).start();
new Thread(new MyThread4()).start();
//所以优先级不要用啦!!!并不一定真正的优先!!!
//线程执行的结果不可控!!!很尴尬!!!
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.qfedu.b_threadfun;

class MyThread5 implements Runnable {
@Override
public void run() {
try {
//发现 Thread.sleep 有一个运行时异常,
//但是发现没有抛出,只有try-catch 为啥?
//sleep方法写在了run方法中了,因为
//run方法是重写的, 父类的 public abstract void run();
//父类有抛出吗?没有抛出,重写是比较严格的
//父类没有抛出,子类也同样不能抛出
Thread.sleep(10000);//10秒
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 500; i++) {
System.out.println("MyThread5:" + i);
}
}
}
class MyThread6 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("MyThread6:" + i);
}
}
}
public class Demo3 {
public static void main(String[] args) {
new Thread(new MyThread5()).start();
new Thread(new MyThread6()).start();
//发现运行的结果是不可控的,是随机的抢占式的
//咱们接下来学习一个东西叫sleep 让某一个线程睡一会儿
//这个线程在睡觉的期间不会去抢占cpu 不执行
//现在我让MyTread5睡了一会儿。就意味着绝对MyThread6线程先执行
//实现可控的效果
//思考:sleep方法再开发的时候敢用不敢用?
//不敢用。睡多久你知道吗?睡多久合适?不能确定
//cpu最大利用化。不可能让cpu闲置
//如果sleep睡眠时间少的话,还是抢占
//如果sleep睡眠你时间太长的话,就会cpu就会闲置
//没有办法把控的!!! 接下来要学习锁!!!
}
}

下午提问方法

上午的内容

1
2
3
4
5
6
7
8
9
10
11
12
1.新建线程的两种方式
1.继承Thread
2.实现Runnable接口【开发要用的】
代码要自己学会写的!!!
2.线程的方法
Thread.currentThread();获取当前线程对象
setPriority();设置优先级的
getPriority(); 获取当前线程的优先级
getName();得到线程的名字
setName();设置线程的名字
sleep();线程的休眠

7.线程的同步和锁【重要】

为什么要进行线程的同步?

Java是允许多线程(多个线程),当多个线程操作同一个资源(咋操作)的时候,会导致得到或者打印的数据不准确。从而发生冲突。咋解决?加同步锁。

1

1
2
3
4
5
6
7
8
9
10
11
美团
淘票票
这个两个线程,都去麦同一场次的票
结果美团卖出去一张1排1列的票
结果淘票票也卖出去了1排1列的票 你感觉合适吗?
就是上面的这种结果!!!不合适的,分享同一个资源的时候,要保证分享资源的数据,合法性!!!



分析结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.qfedu.c_sync;

//最理想的状态!!!
//先线程1进入到ticket=50,循环 循环结束以后 此时
//tiket=49了
//循环第二次的时候 线程2抢到资源了 此时ticket=49
//循环 打印49 tiket-- ticket=48了
//循环第三次的时候 线程2 抢到资源了, 此时ticket=48
//打印卖第48张票。ticket-- tiket=47
//线程1又抢到循环
//现在的情况是:有可能两个线程同时进入到while循环
//
class MySync implements Runnable {
int ticket = 50;
@Override
public void run() {
//
while (true) {//死循环
//两个线程都进入到了循环了
//此时两个线程所持有的ticket 都是50
//但是两个线程都要往下执行
//有可能线程1 先执行了sout(50) 线程2在等待哦!!!
//线程1执行了--操作并出了循环 线程1ticket = 49
//线程1又抢到循环了 sout(49) tiket--
//再进入倒这个循环,有可能线程2抢到这个执行权
//线程2要往下执行输出语句 ticket=50 打印50
if (ticket > 0) {
//线程具有抢占式的运行
//咱们有没有可能,线程3进入到if语句
//此时线程1也进入到if语句了
//线程3去打印 卖出了50张票
//在线程1里面 ticket=50
//线程3又抢到ticket-- 又进入到循环了 ticket = 49
//线程3又抢到了ticket-- 又进入倒循环 ticket=48
//线程1又抢到资源要执行,执行输出语句 tiekct=50
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "票");
ticket--;
} else {
System.out.println("买完了");
break;//终止循环!!!
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
MySync mySync = new MySync();
//这三个线程
Thread thread1 = new Thread(mySync, "线程1");
thread1.start();
Thread thread2 = new Thread(mySync, "线程2");
thread2.start();
Thread thread3 = new Thread(mySync, "线程3");
thread3.start();


}
}

解决方案:

​ 1.同步方法:使用一个关键字synchronized修饰方法。因为Java对象都有一个内置的锁对象。当使用这个关键字的时候,修饰方法的时候,这个方法就会被锁保护起来被锁锁住

当一个线程进来以后,会立马锁住当前的方法。意味着只有一个线程进来,其他线程都在外面等着。

1
2
3
public synchronized  void run () {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.qfedu.c_sync;

//最理想的状态!!!
//先线程1进入到ticket=50,循环 循环结束以后 此时
//tiket=49了
//循环第二次的时候 线程2抢到资源了 此时ticket=49
//循环 打印49 tiket-- ticket=48了
//循环第三次的时候 线程2 抢到资源了, 此时ticket=48
//打印卖第48张票。ticket-- tiket=47
//线程1又抢到循环
//现在的情况是:有可能两个线程同时进入到while循环
//
class MySync1 implements Runnable {
int ticket = 50;
//对这个run方法加了锁 就意味着只有一个线程进入到run方法中
//其他线程都在run方法外面等待
@Override
public synchronized void run() {
//
while (true) {//死循环
//两个线程都进入到了循环了
//此时两个线程所持有的ticket 都是50
//但是两个线程都要往下执行
//有可能线程1 先执行了sout(50) 线程2在等待哦!!!
//线程1执行了--操作并出了循环 线程1ticket = 49
//线程1又抢到循环了 sout(49) tiket--
//再进入倒这个循环,有可能线程2抢到这个执行权
//线程2要往下执行输出语句 ticket=50 打印50
if (ticket > 0) {
//线程具有抢占式的运行
//咱们有没有可能,线程3进入到if语句
//此时线程1也进入到if语句了
//线程3去打印 卖出了50张票
//在线程1里面 ticket=50
//线程3又抢到ticket-- 又进入到循环了 ticket = 49
//线程3又抢到了ticket-- 又进入倒循环 ticket=48
//线程1又抢到资源要执行,执行输出语句 tiekct=50
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "票");
ticket--;
} else {
System.out.println("买完了");
break;//终止循环!!!
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
MySync1 mySync = new MySync1();
//这三个线程
Thread thread1 = new Thread(mySync, "线程1");
thread1.start();
Thread thread2 = new Thread(mySync, "线程2");
thread2.start();
Thread thread3 = new Thread(mySync, "线程3");
thread3.start();

//为啥都是线程1卖出去的票?
//很巧 线程1抢到执行权了,进入到run方法中
//线程2和线程3在外面等着。
//一个循环进来以后,把循环全部执行完!!!
//会出现一家独大的情况!!!也是不符合咱们生活场景的!!!
//咋解决?咱们 不能方法中加锁,在其他地方加锁


}
}

换另外一种解决方法:

​ 同步代码块:就是拥有了synchronized 关键字修饰一个语句块。被修饰的语句块会被加锁。从而实现同步。

语法格式:

1
2
3
synchronized  (this) {
被加锁的代码块
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.qfedu.c_sync;

//最理想的状态!!!
//先线程1进入到ticket=50,循环 循环结束以后 此时
//tiket=49了
//循环第二次的时候 线程2抢到资源了 此时ticket=49
//循环 打印49 tiket-- ticket=48了
//循环第三次的时候 线程2 抢到资源了, 此时ticket=48
//打印卖第48张票。ticket-- tiket=47
//线程1又抢到循环
//现在的情况是:有可能两个线程同时进入到while循环
//
class MySync2 implements Runnable {
int ticket = 500;
//对这个run方法加了锁 就意味着只有一个线程进入到run方法中
//其他线程都在run方法外面等待
@Override
public void run() {
//能不能对循环加锁?不能 因为循环加锁以后,还是一个线程循环完,没有任何意义

while (true) {//死循环
//if语句加了锁以后
//就意味着只有一个线程进入到if语句
//假如线程1进入if语句了,线程2和线程3就会等待
//线程1打印50 并-- ticket变量为49
//线程2抢到了49 sout(49) tiket-- 48
//其他线程再抢!!!

//核心业务 加了锁,只让一个线程进入,操作完以后。锁释放掉
//然后这三个线程再抢。还只能进一个,再操作核心业务
synchronized (this) {//只能让一个线程进入操作,其他线程在外面等待排队
if (ticket > 0) {
//线程具有抢占式的运行
//咱们有没有可能,线程3进入到if语句
//此时线程1也进入到if语句了
//线程3去打印 卖出了50张票
//在线程1里面 ticket=50
//线程3又抢到ticket-- 又进入到循环了 ticket = 49
//线程3又抢到了ticket-- 又进入倒循环 ticket=48
//线程1又抢到资源要执行,执行输出语句 tiekct=50
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "票");
ticket--;
} else {
System.out.println("买完了");
break;//终止循环!!!
}
}
}
}
}
public class Demo3 {
public static void main(String[] args) {
MySync2 mySync = new MySync2();
//这三个线程
Thread thread1 = new Thread(mySync, "线程1");
thread1.start();
Thread thread2 = new Thread(mySync, "线程2");
thread2.start();
Thread thread3 = new Thread(mySync, "线程3");
thread3.start();

//为啥都是线程1卖出去的票?
//很巧 线程1抢到执行权了,进入到run方法中
//线程2和线程3在外面等着。
//一个循环进来以后,把循环全部执行完!!!
//会出现一家独大的情况!!!也是不符合咱们生活场景的!!!
//咋解决?咱们 不能方法中加锁,在其他地方加锁


}
}

针对于同步代码块举个例子:

​ 上厕所的时候,有坑位,这个坑位就是资源。

​ 三个人去抢这个资源,如果不加锁的话,会出现问题的?是的。加上锁以后就会保证数据准确性。

案例:

1
2
3
4
5
6
7
加锁的目的为了保证数据的准确性。
卖电影票:
三个线程:
淘票票
美团
猫眼
100张票
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.qfedu.c_sync;

class SaleTicket implements Runnable {
//声明一个变量票
//静态的变量和对象没有关系了
private static int ticket = 100;
@Override
public void run() {
//美团



while (true) {
//美团 猫眼 淘票票
synchronized (this) {
if (ticket > 0) {
//淘票票 和猫眼同时进入到if语句,但是都没有执行ticket--这个操作
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "票");
//所以会打印 淘票票 100 猫眼100

ticket--;
} else {
System.out.println("卖完了");
break;
}
}
}

}
}
public class Demo4 {
public static void main(String[] args) {
SaleTicket saleTicket = new SaleTicket();
new Thread(saleTicket, "淘票票").start();
new Thread(saleTicket, "美团").start();
new Thread(saleTicket, "猫眼").start();
//淘票票卖出了第100票
//猫眼卖出了第100票
//淘票票卖出了第99票
//淘票票卖出了第97票
//淘票票卖出了第96票
//淘票票卖出了第100票
//淘票票卖出了第99票
//猫眼卖出了第98票
//猫眼卖出了第97票

}
}

线程就是这样,不可控制,但是可以加锁。让他可控制。

day23

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1.创建线程地两种方式
1.
继承Thread类
在main主函数中 实例化Thread的子类,然后调用方法start方法启动线程
2.
实现Runnable接口
实例化Thread类,实现Runnable接口对象的作为Thread的参数,然后调用start方法
2.线程下面地方法
Thread.currentThread 获取当前线程对象的
setName 设置线程名字
getName 获取线程的名字
Thread.sleep 让线程进行休眠的

3.File类下面地方法
4.String类下面地方法

今天的内容

1.守护线程

2.死锁

3.线程的生命周期

4.关于Object类下面的方法和线程有关

5.生产者消费者模式【难点】

1.守护线程

守护线程是用来守护非守护线程的。

非守护线程:就是平常写的线程

一个应用程序中必须至少一个非守护线程

非守护线程一旦结束,守护线程就会自动消亡

古代皇帝(非守护线程),古代皇帝的妃子(守护线程)

一旦皇帝死了,妃子要陪葬!!!

守护线程依附非守护线程,如果非守护线程消亡,那么守护线程随之消亡。

main 主函数是非守护线程

真实开发的时候:

​ 后台记录操作日志,监控内存,垃圾回收等 都可以使用守护线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.qfedu.a_daemon;

class MyThread1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("子线程正在执行" + i);
}
}
}
public class Demo1 {
public static void main(String[] args) {//在非守护线程中执行的
//启动线程
MyThread1 myThread1 = new MyThread1();
Thread thread = new Thread(myThread1);
//可以将myThread1设置为守护线程。
//就会在非守护线程结束De一瞬间,守护线程也会停止执行
thread.setDaemon(true);//变成了守护线程了
thread.start();
for (int i = 0; i < 200; i++) {
System.out.println("主线程正在执行" + i);
}
//在jvm中 main主线程, 还有一个线程 垃圾回收线程
//等到main主线程和垃圾回收线程 都结束De时候才守护线程才消亡!!!

}
}

2.死锁

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁线程。

你的钥匙丢完了。进不了门?咋办?找开锁公司。

开锁公司说你得给我提供你的身份证原件。我才能给你开锁。

你说你得先开门,我的身份证原件在屋里锁着呢。

结果你们两个僵持不下,导致开门这个事推进不了

开发中禁止使用死锁。

面试会问:

应用场景:并发场景,多线程。线程之间互不相让。得借助锁。

加锁的目的是为了线程安全,但是物极必反。尤其是加了锁以后。

死锁是一种状态,当两个线程互相持有对方的资源的时候,却又不主动释放这个资源的时候。会导致死锁。这两个线程就会僵持住。代码就无法继续执行。

线程1 有锁1

线程2 锁2

线程1会等待锁2 的释放

线程2会等待锁1的释放

1

面试题:

​ 手写一个死锁!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.qfedu.b_deadThread;

class DeadLock implements Runnable {
private boolean flag;//标记属性
private Object obj1;//锁住的对象
private Object obj2;//锁住的对象

public DeadLock(boolean flag, Object obj1, Object obj2) {
this.flag = flag;
this.obj1 = obj1;
this.obj2 = obj2;
}

@Override
public void run() {
//true
if (flag) {//如果设置为true,就让线程1进入到if语句中
synchronized (obj1) {//锁住的是obj1对象
//线程1持有obj1锁
System.out.println(Thread.currentThread().getName() + "拿到了锁1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("等待锁2的释......");
//我想在线程1中去使用线程2中的那个锁2 obj2
//线程1里面想用obj2锁对象
//也走不下去了
//线程1也没有释放obj1
synchronized (obj2) {
System.out.println("123");
System.out.println(Thread.currentThread().getName() + "拿到了锁1");

}
}
}
if (!flag) {//如果设置为false,就让线程2进入到if语句中
synchronized (obj2) {//锁住的是obj2对象
//线程2持有obj2这个锁
System.out.println(Thread.currentThread().getName() + "拿到了锁2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("等待锁1的释......");
//只有obj1释放掉以后,才能在线程2中对obj1加锁
//想一个问题,如果obj1锁对象没有被释放,那么下面这个代码
//线程2中去锁obj1
//在这等着呢 往下走不下去了 线程2没有释放obj2对象
synchronized (obj1) {
System.out.println("456");
System.out.println(Thread.currentThread().getName() + "拿到了锁1");
}
}
}

}
}
public class Demo1 {
public static void main(String[] args) {
Object obj1 = new Object();
Object obj2 = new Object();
//线程1可以进入到run方法中 if (flag)
DeadLock deadLock = new DeadLock(true, obj1, obj2);
new Thread(deadLock, "线程1").start();
//线程2 可以进入倒run方法中if(!flag)
DeadLock deadLock1 = new DeadLock(false, obj1, obj2);
new Thread(deadLock1, "线程2").start();
}
}

3.线程生命周期【面试】

1)新建:当一个Thread类或其子类的对象被声明并创建时。新生的线程对象属于新建状态。

(2)就绪(可运行状态):处于新建状态的线程执行start()方法后,进入线程队列等待CPU时间片,该状态具备了运行的状态,只是没有分配到CPU资源。

(3)运行:当就绪的线程分配到CPU资源便进入运行状态,run()方法定义了线程的操作。

(4)阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的的执行,进入阻塞状态。

(5)死亡:当线程执行完自己的操作或提前被强制性的终止或出现异常导致结束,会进入死亡状态。

2

4.和线程相关的Object类下面的方法

Object类下面的方法:

1
2
public final void wait()
throws InterruptedException
void notify()唤醒正在等待对象监视器的单个线程。
void notifyAll()唤醒正在等待对象监视器的所有线程。

、导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法。 换句话说,这个方法的行为就好像简单地执行呼叫wait(0)

总结:至少两个线程,其中一个线程中使用对象.wait() 那么这个线程就会阻塞,代码不会往下执行了。如何想让这个线程往下执行呢?再开另外一个线程,使用对象.notify()去唤醒另外那个等待线程。

3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package com.qfedu.c_wait;

//创建这个类的目的,就是实例化出来对象,然后拿这个对象
//调用wait方法和notify方法
//wait方法和notify方法是object对象的方法
class Message {
private String message;

public Message(String message) {

this.message = message;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

@Override
public String toString() {
return "Message{" +
"message='" + message + '\'' +
'}';
}

}
//导致当前线程等待,直到另一个线程调用该对象的[`notify()`
class WaiterThread implements Runnable {
//想一个问题?WaiterThread 使用message对象调用
//wait() 咋解决?
private Message msg;

public WaiterThread(Message msg) {
this.msg = msg;
}

@Override
public void run() {
//先获取当前线程名字
String name = Thread.currentThread().getName();
System.out.println(name + "等待唤醒时间:" +System.currentTimeMillis());
//让等待线程去阻塞,去等待 这个线程执行不下去了
//锁的是msg对象
synchronized (msg) {//为啥是哟个wait的时候要加锁?等会将
try {
msg.wait();//代码走到这,当前这个线程阻塞,不往下走了
//咱们得想办法让这个等待线程继续执行下去,咋办?
//在另外一个线程中去调用notify方法那么等待线程就会执行下去
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("123");
System.out.println(name + "被唤醒的时间:" + System.currentTimeMillis());
}


}
}
//唤醒线程
class NotifyThread implements Runnable {
//也要用同一个对象是WaiterThread线程中同一个对象调用notify()方法
private Message msg;

public NotifyThread(Message msg) {
this.msg = msg;
}
@Override
public void run() {
try {
//我的想法是不能先让唤醒线程执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "开始唤醒等待线程");
synchronized (msg) {
msg.setMessage("我是修改之后的message值");
//msg.notify();
msg.notifyAll();//唤醒所有线程
}

}
}
public class Demo1 {
public static void main(String[] args) {
Message message = new Message("我是message属性");
WaiterThread waiterThread = new WaiterThread(message);
NotifyThread notifyThread = new NotifyThread(message);
//如果等待线程好几个 咋办呢?
new Thread(waiterThread, "等待线程1").start();
new Thread(waiterThread, "等待线程2").start();
new Thread(waiterThread, "等待线程3").start();
new Thread(notifyThread, "唤醒线程").start();

//等待线程等待唤醒时间:1660187660718 等待线程
//唤醒线程开始唤醒等待线程 唤醒线程
//123 等待线程
//等待线程被唤醒的时间:1660187661740 等待线程
//这叫线程之间的通信问题!!!
}
}

总结:

1
2
3
4
5
6
新建两个线程:
一个是等待线程
线程里面的代码从上往下执行的,但是使用object.wait(),就这个方法一用,你的线程就
阻塞了,就处于等待状态。意味着当前的代码到了wait方法以后的代码暂时不执行了
另外一个是唤醒线程。
唤醒线程中使用object.notify()方法,这个方法是专门唤醒刚才那个等待线程。让等待线程继续执行

思考:

1
2
3
4
5
6
7
8
9
10
11
在使用wait和notify的时候都加了锁 为啥?
因为我要知道对那个对象进行唤醒的!!!
再举个生活中的例子:
大学的时候在楼底下等过自己的女朋友。
你就是等待线程
你女朋友就是唤醒线程
你在楼底下等的时候 就是在wait。
你女朋友下楼之后,唤醒你 咱们走吧。
但是你女朋友 去唤醒别人的男朋友?这就扯犊子了,所有加锁de保证是同一个对象的

wait需要持有锁的原因是,你肯定需要知道在哪个对象上进行等待,如果不持有锁,将无法做到对象变更时进行实时感知通知的作用。与此同时,为了让其他线程可以操作该值的变化,它必须要先释放掉锁,然后在该节点上进行等待。不持有锁而进行wait,可能会导致长眠不起。而且,如果不持有锁,则当wait之后的操作,都可能是错的,因为可能这个数据已经过时,其实也叫线程不安全了。总之,一切为了安全,单独的wait做不成这事。

扩展

1
2
3
4
5
6
7
Thread类join方法
join()方法,因为join()方法底层是就是通过wait()方法实现的。
让"主线程"等待(WAITING状态),一直等到其他线程不再活动为止,然后"主线程"再执行
join在英语中是“加入”的意思,join()方法要做的事就是,当有新的线程加入时,主线程会进入等待状态,一直到调用join()方法的线程执行结束为止。
面试题:
如何先让所有的子线程执行,最后再执行主线程,咋解决?
join!!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.qfedu.c_wait;

class MyThread1 implements Runnable {

@Override
public void run() {
//join方法的作用是阻塞,即等待线程结束,才继续执行。
//如果使用了Thread.currentThread().join()之后
//一直阻塞,无法终止
// Thread thread = Thread.currentThread();
// try {
// thread.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":"+i);
}
}
}
class MyThread2 implements Runnable {

@Override
public void run() {

for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":"+ i);
}
}
}

public class Demo2 {
public static void main(String[] args) throws InterruptedException {

//main是主线程!!!
Thread thread1 = new Thread(new MyThread1(), "MyThread1");
thread1.start();
thread1.join();
Thread thread2 = new Thread(new MyThread2(), "MyThread2");
thread2.start();

thread2.join();
//让"主线程"等待(WAITING状态),一直等到其他线程不再活动为止,然后"主线程"再执行
//所以这次打印的结果都是主线程在最下面!!!
//加join的目的是可以让主线程和子线程可控
//如果不加join的话,主线程和子线程随机交叉打印!!!
//2204班有个面试题:
// 先让所有的子线程执行,最后再执行主线程,咋出来?join!!!

for (int i = 0; i < 100; i++) {
System.out.println("主线程:" + i);
}

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.qfedu.c_wait;

class Father implements Runnable {

@Override
public void run() {
//son是一个线程 Father是一个线程
//但是Father 线程的run方法里面调用了Son线程
//在main主函数中,只需要启动Father线程,在Father线程
//又执行了Son线程,所以可堪看成Father 是Son的父线程(主线程)

Son son = new Son();
Thread thread = new Thread(son);
thread.start();
try {
//加了join之后,thread 是son线程,son线程对应的主线程(Father)
//会等待,等待子线程执行完以后才执行的
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
System.out.println("Father线程:" + i);
}
}
}
class Son implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("son线程:" + i);
}
}
}
public class Demo3 {
public static void main(String[] args) {
Father father = new Father();
Thread thread = new Thread(father);
thread.start();
}
}

5.生产者消费者模式【重点难点】

生活中例子:

​ 卖家:BYD汽车厂商

​ 买家: 咱们75名学生

​ 学虎想买一辆 比亚迪汉 , 告知汽车厂商我要买车。这个学虎会进入到等待状态

​ 等到比亚迪厂家造完车以后,再通知学虎来提车。如果比亚迪厂家有现车,学虎就直接提车。

​ 如果产品需要生产的话,消费者进入到阻塞状态 wait

​ 如果产品不需要生产的话,消费者直接购买

这些会牵涉到咱们刚讲过的wait和notify方法!!!

只不过业务逻辑又复杂了啊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package com.qfedu.d_procus;

//为啥要新建这类Goods?
//还记得上午讲的Message类
//因为两个线程要通信。这个Goods就是通信的桥梁!!!
//goods.wait() 消费者等待
//goods.notoify() 生产者唤醒消费者
class Goods {
private String name;//商品名字
private double price;//商品价格
private boolean isProduct;//
//isProduct是否有这个商品, true 没有这个产品需要生产
//false 有这个产品,不需要生产
//有参构造


public Goods(String name, double price, boolean isProduct) {
this.name = name;
this.price = price;
this.isProduct = isProduct;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

public boolean isProduct() {
return isProduct;
}

public void setProduct(boolean product) {
isProduct = product;
}

@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
", isProduct=" + isProduct +
'}';
}
}
//接下来搞两个线程?一个消费者线程 一个是生产者线程
class Customer implements Runnable {
//为啥要定义这个goods变量? 因为两个线程要共享同一个资源!!!

private Goods goods;

public Customer(Goods goods) {
this.goods = goods;
}

@Override
public void run() {
//写业务逻辑,业务比较麻烦
while (true) {//一直消费
synchronized (goods) {
//goods.isProduct true 需要生产,没有商品 false 不需要生产
if (!goods.isProduct()) {
//不需要生产的,消费者直接购买
System.out.println("消费者购买了:" + goods.getName() + ",价格为:" + goods.getPrice());
//购买完以后 商品没有了 将isProduct这个变量修改为true
goods.setProduct(true);
//大家有没有想过这个问题?消费者在购买的时候,生产者等待
//唤醒生产者去生产车了
goods.notify();//可以先防一下,等会再看!!!


} else {
//需要生产的!!!,没商品(咋办)
//消费者进入到阻塞状态!!!
try {
goods.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}

}

}
}

}
}
class Producter implements Runnable {
//为啥要定义这个goods变量?
private Goods goods;
public Producter(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
int count = 0;
//生产者,业务逻辑比较麻烦
while (true) {//你一直消费,我一直生产
synchronized (goods) {
if (goods.isProduct()) {//true 需要生产的!!
//造车,就是赋值 对goods对象进行赋值
//奇数造一种车, 偶数造另外一种车
if (count % 2 == 0) {//偶数
goods.setName("奥迪A8");
goods.setPrice(78.9);
} else {//奇数
goods.setName("五菱大光");
goods.setPrice(12.9);
}
//生产一辆车,一定要记得有车了
//标记就改为 false 就证明不需要再生产了
goods.setProduct(false);
System.out.println("生产者生产了:" + goods.getName() + ",价格为:" + goods.getPrice());

count++;
//生产完以后,唤醒消费者。让消费者去提车
goods.notify();

} else {
//不需要生产
//不需要生产 有货,生产着歇着,阻塞
try {
goods.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
Goods goods = new Goods("东风41", 67.8, false);
Producter producter = new Producter(goods);
new Thread(producter).start();

Customer customer = new Customer(goods);
new Thread(customer).start();
/**
* 谁先抢到线程?消费者?还是生产者?
* //如果是消费者抢到执行权,不用说!!!直接打印消费者购买了东风
* //如果生产者抢到执行权,生产者wait,那就意味着必须去执行消费者线程
* 消费者购买了:东风41,价格为:67.8
* //此时isProduct是true 需要时生产
* 还是消费者和生产者抢这个执行权
* //假如生产者抢到了 就会打印生产者生产了啥。
* //假如消费者抢到了执行权,消费者进入倒阻塞状态!!!
* //消费者进入倒阻塞,那么肯定生产者得执行了
* 生产者生产了:奥迪A8,价格为:78.9
*
* 还是两个线程抢这个执行权
* 消费者购买了:奥迪A8,价格为:78.9
* 生产者生产了:五菱大光,价格为:12.9
* 消费者购买了:五菱大光,价格为:12.9
* 生产者生产了:奥迪A8,价格为:78.9
* 消费者购买了:奥迪A8,价格为:78.9
* 生产者生产了:五菱大光,价格为:12.9
* 消费者购买了:五菱大光,价格为:12.9
* 生产者生产了:奥迪A8,价格为:78.9
* 消费者购买了:奥迪A8,价格为:78.9
*/
}
}

这个东西你是需要沉淀!!!,你一定要紧紧围绕者线程抢占式执行的,然后分析结果!!!

6.线程池Pool

线程池一个容纳了多个线程的容器,其中的线程可以反复的使用。省去了频繁创建线程的对象的操作,无需反复创建线程而消耗更多的资源

工作原理:

1

在 Java 语言中,并发编程都是通过创建线程池来实现的,而线程池的创建方式也有很多种,每种线程池的创建方式都对应了不同的使用场景,总体来说线程池的创建可以分为以下两类:

通过 ThreadPoolExecutor 手动创建线程池。

通过 Executors 执行器自动创建线程池。

而以上两类创建线程池的方式,又有 7 种具体实现方法,这 7 种实现方法分别是:

1.Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。

2.Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。

3.Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。

4.Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。

5.Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。

6.ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。

接下来我们分别来看这 7 种线程池的具体使用。

1.1FixedThreadPool

中文翻译固定的线程池

创建一个固定大小的线程池,可控制并发线程数。使用 FixedThreadPool 创建 2 个固定大小的线程池,具体实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.qfedu.c_object;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo3 {
public static void main(String[] args) {
fixedThreadPool();
}
public static void fixedThreadPool() {
// 创建 2 个线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);

// 创建任务
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
}
};

// 线程池执行任务(一次添加 4 个任务)
// 执行任务的方法有两种:submit 和 execute
threadPool.submit(runnable); // 执行方式 1:submit
threadPool.execute(runnable); // 执行方式 2:execute
threadPool.execute(runnable);
threadPool.execute(runnable);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.qfedu.e_threadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo2 {
public static void main(String[] args) {

//1.创建线程池 创建了两个线程
ExecutorService threadPool = Executors.newFixedThreadPool(2);

//2.任务
Runnable run1 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("晚上吃饭。线程为:" + Thread.currentThread().getName());

}
}
};
Runnable run2 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("晚上睡觉。线程为:" + Thread.currentThread().getName());

}
}
};
Runnable run3 = new Runnable() {
@Override
public void run() {
//run方法中写的是需求!!
for (int i = 0; i < 20; i++) {
System.out.println("晚上敲代码不睡觉。线程为:" + Thread.currentThread().getName());

}
}
};
//3.执行上上面的三个任务
threadPool.submit(run1);
threadPool.execute(run2);
threadPool.execute(run3);

}
}

2.CachedThreadPool

可以缓存的线程池

创建一个可缓存的线程池,若线程数超过任务所需,那么多余的线程会被缓存一段时间后才被回收,若线程数不够,则会新建线程。CachedThreadPool 使用示例如下:

线程数超过了任务数。有四个线程 但是任务有两个 只需要两个线程就可以了,其他两个线程回收

线程数低于任务数,新建线程!!!自动新建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.qfedu.c_object;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Demo3 {
public static void main(String[] args) {
cachedThreadPool();
}
public static void cachedThreadPool() {
// 创建线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
// 执行任务
for (int i = 0; i < 10; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}
});
}
}
}

线程池创建了 10 个线程来执行相应的任务。

使用场景

CachedThreadPool 是根据短时间的任务量来决定创建的线程数量的,所以它适合短时间内有突发大量任务的处理场景。

3.SingleThreadExecutor

创建单个线程的线程池,它可以保证先进先出(队列)的执行顺序。SingleThreadExecutor 使用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.qfedu.c_object;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Demo3 {
public static void main(String[] args) {
singleThreadExecutor();
}

public static void singleThreadExecutor() {
// 创建线程池
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 执行任务
for (int i = 0; i < 10; i++) {
final int index = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index + ":任务被执行");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}
});
}
}
}

单个线程的线程池有什么意义?

单个线程的线程池相比于线程来说,它的优点有以下 2 个:

  • 可以复用线程:即使是单个线程池,也可以复用线程。
  • 提供了任务管理功能:单个线程池也拥有任务队列,在任务队列可以存储多个任务,这是线程无法实现的,并且当任务队列满了之后,可以执行拒绝策略,这些都是线程不具备的。

4.ScheduledThreadPool

创建一个可以执行延迟任务的线程池。使用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.qfedu.c_object;

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Demo3 {
public static void main(String[] args) {
scheduledThreadPool();
}

public static void scheduledThreadPool() {
// 创建线程池
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
// 添加定时执行任务(1s 后执行)
System.out.println("添加任务,时间:" + new Date());
threadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务被执行,时间:" + new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}
}
, 3, TimeUnit.SECONDS);
}

}

上述结果可以看出,任务在 3 秒之后被执行了,实现了延迟 3s 再执行任务

5.SingleThreadScheduledExecutor

创建一个单线程的可以执行延迟任务的线程池,此线程池可以看作是 ScheduledThreadPool 的单线程池版本。它的使用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
ublic static void SingleThreadScheduledExecutor() {
// 创建线程池
ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
// 添加定时执行任务(2s 后执行)
System.out.println("添加任务,时间:" + new Date());
threadPool.schedule(() -> {
System.out.println("任务被执行,时间:" + new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}, 2, TimeUnit.SECONDS);
}

上述结果可以看出,任务在 2 秒之后被执行了

6.newWorkStealingPool

创建一个抢占式执行的线程池(任务执行顺序不确定),此方法是 JDK 1.8 版本新增的,因此只有在 JDK 1.8 以上的程序中才能使用。newWorkStealingPool 使用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.qfedu.c_object;

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Demo3 {
public static void main(String[] args) {
workStealingPool();
}

public static void workStealingPool() {
// 创建线程池
ExecutorService threadPool = Executors.newWorkStealingPool();
// 执行任务
for (int i = 0; i < 10; i++) {
final int index = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName());
}
});
}
// 确保任务执行完成
while (!threadPool.isTerminated()) {
}
}
}

从上述结果可以看出,任务的执行顺序是不确定的,因为它是抢占式执行的。

以上都不用!!!

7.ThreadPoolExecutor【以后开发要用的】

ThreadPoolExecutor 是最原始、也是最推荐的手动创建线程池的方式,它在创建时最多提供 7 个参数可供设置。这7个参数面试问!!! 很哈皮!!!

ThreadPoolExecutor 使用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.qfedu.e_threadpool;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Demo5 {
public static void main(String[] args) {
//这个是必须要会理解的!!!

//创建线程池
//public ThreadPoolExecutor(int corePoolSize,
// int maximumPoolSize,
// long keepAliveTime,
// TimeUnit unit,
// BlockingQueue<Runnable> workQueue,
// ThreadFactory threadFactory,
// RejectedExecutionHandler handler)
//corePoolSize :核心线程数 5个 一个公司最大容量10人 核心只有5个人 只有这5个人干活
//maximumPoolSize:线程池最多可以运行线程数 设置为10 约 5个亿左右
//keepAliveTime:存活时间,空闲下来的线程得回收。按照秒来计算。活动状态,比较活跃 不回收
//如果你不干活超过100秒,我就会回收掉这个线程!!!
//unit 是keepAliveTime 单位 可以是秒 小时 天 毫秒, 纳秒!!!
//workQueue 阻塞队列
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10));

//执行任务
for (int i = 0; i < 6; i++) {
final int index =i;
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index +"被执行线程名:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}

1
2
3
4
5
6
7
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
尝试去看懂这个东西,看不懂的话,就再等等,只要找工作前能理解透就可以
ThreadPoolExecutor的创建主要参数有7个,接下来将进行一一介绍。

最大线程数(MaximumPoolSize)和核心线程数(CorePoolSize)

最大线程数(MaximumPoolSize):线程池运行的最大线程数量,受属性CAPACITY的限制,最大为(2^29)-1(约5亿)
核心线程数(CorePoolSize):线程池中保持最小活动数的线程数量,并且不允许超时,除非调用allowCoreThreadTimeOut方法,这个时候最小值是0。
当线程池线程数量小于核心线程数时,一个新的任务请求被提交上来时,不管其他线程是否处于空闲状态,都会新建一个线程来处理这个请求。
如果在运行的线程数数量超过核心线程数但是小于最大线程数,并且工作队列已满,将创建一个线程处理这个请求。
默认情况下,当一个任务请求时,核心线程数才会被创建和启动,但是也可以通过prestartCoreThread启动一个核心线程或者prestartAllCoreThread启动所有核心线程。

创建新的线程(ThreadFactory)

ThreadFactory用来创建线程。如果没有指定ThreadFactory的话,默认会使用Executors#defaultThreadFactory 来创建线程,并且这些线程都是在同一个ThreadGroup并且都是非守护线程状态(non-daemon status)并且拥有相同的优先级(NORM_PRIORITY)。 如果指定了ThreadFactory 可以修改ThreadGroup和线程的名称、守护状态、优先级等。
ThreadFactory如果调用newThread(Runnable r)方法返回null则创建线程失败,线程池会继续运行但可能不会执行任何任务。
线程应该拥有"modifyThread"权限,如果工作线程或者其他线程没有拥有这个权限,服务可能会降级,配置更改可能不会及时生效,关闭线程池可能保持在可能终止但未完成的状态。

存活时间(Keep-alive times)

存活时间(Keep-alive times):空闲线程等待工作的超时时间(以纳秒为单位)
如果当前线程池中的线程数超过了核心线程数,超出的部分线程如果空闲的时长大于存活时长,那么他们将会被终止运行。当线程池不被频繁使用的时候,这提供了一种减少资源消耗的方法。存活时间可以通过setKeepAliveTime(long, TimeUnit)进行修改,使用 setKeepAliveTime(Long.MAX_VALUE, NANOSECONDS)有效地禁止空闲线程在关闭之前终止。默认情况下,存活策略只适用于当前线程数超过核心线程数的情况下。
但是使用方法allowCoreThreadTimeOut(boolean)也可以将这个超时策略应用到核心线程,只要keepAliveTime值不为零。

时间单位(TimeUnit)

TimeUnit 是存活时间的单位。

阻塞队列(BlockingQueue)

任何实现了BlockingQueue接口的实现类都可以用来传输和保存提交的任务,阻塞队列的使用和线程池大小相关:

如果运行的线程少于核心线程数, Executor总是倾向于添加一个新线程而不是排队
如果核心线程数(5个)或更多线程正在运行(不超过最大线程数)(10个),Executor总是倾向于排队请求(从5个核心线程中去拿线程排队拿),而不是添加一个新线程
如果没有达到最大线程数并且队列未满,将创建新的线程执行任务,如果线程数大于最大线程数,任务将会被拒绝
三种排队策略

直接传递
工作队列的一个很好的默认选择是 SynchronousQueue,它将任务交给线程而不用其他方式持有它们。一个新的任务尝试排队时,如果没有可供使用的线程运行它时将会创建一个新的线程。该策略避免了锁定处理可能具有内部依赖关系的请求集,直接传递通常需要无界的最大线程池来避免新的任务提交。这反过来又承认了当命令的平均到达速度快于它们的处理速度时,线程无限增长的可能性。

无界队列
无界队列是一个没有预定义容量的队列,使用无界队列例如LinkedBlockingQueue将导致新任务一直在等待,当核心线程数(5个)的线程处于工作状态时。因此,不会有超过核心线程数的线程被创建(),也就是说最大线程数是不起作用的。当任务之间互相独立,互不影响的时候这个选择可能是挺合适的。例如,在web服务器中,这种队列在消除短暂的高并发方面很有作用,它允许无界队列增长的平均速度比处理的平均速度快。

有界队列
无界队列例如ArrayBlockingQueue,它能在有限的最大线程数内防止资源耗尽,但是它也更难调整和控制。
队列的大小和最大线程数可以互相替换:使用更大的队列数量和小的线程池数量能够最小化CPU的使用、系统资源和上下文切换的开销,但也人为的导致了低吞吐量。如果一个任务频繁的阻塞,例如频繁I/O,系统更多的时间是在频繁的调度而不是运行任务。使用小的队列通常需要大的线程池数量,这会让CPU更能充分利用,但是也会遇到不可接受的调度开销,也会降低吞吐量。

拒绝任务

在调用execute(Runnable)提交任务时,在Executor已经关闭或者有界队列的最大线程数和队列满的情况下任务会被拒绝。不论在什么情况下,execute方法调用RejectedExecutionHandler#rejectedExecution(Runnable, ThreadPoolExecutor)任务都会根据拒绝策略被拒绝。

四种拒绝策略

ThreadPoolExecutor预定义了四种拒绝策略:

ThreadPoolExecutor.AbortPolicy,默认的拒绝策略,简单粗暴,拒绝的时候直接抛RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy,由调用者执行自身execute方法来运行提交进来的任务,从名字CallerRuns(调用者运行)中就可以看出。它会提供一个简单的反馈控制机制,这种策略将降低新任务被提交上来的速度。
ThreadPoolExecutor.DiscardPolicy,也很简单粗暴,直接丢弃任务,不抛异常。
ThreadPoolExecutor.DiscardOldestPolicy,DiscardOldest丢弃最早的任务,在队列头部也就是最新进入队列的任务会被丢弃,然后尝试提交新任务,如果提交失败会继续重复以上步骤。
也可以自己实现RejectedExecutionHandler接口,并重写rejectedExecution方法来自定义拒绝策略。

通俗解释

关于上面的参数我试着通俗的说一下,希望我说的能让你明白。
假如现在有一家外包公司(ThreadPoolExecutor),公司的核心开发(corePoolSize)有5个人,公司最多容纳(maximumPoolSize)10个开发,现在公司接了一个项目,核心开发还忙的过来,就将这个项目给其中一个核心开发做,慢慢的销售人员接的项目越来越多,5个核心开发都在做项目没时间再做新的项目,公司为了节省开支新来的项目只能先接过来暂时积压(BlockingQueue)起来,但是一直积压也不是个事情,客户也会一直催,公司顶住最多只能积压5个,积压到5个之后公司也还能容纳5个开发,不得不再招人(创建新的线程)处理新的项目。当公司发展的越来越好,接的项目也越来越多这10个开发也忙不过来了,有新的项目再进来就只能通过各种方式拒绝(RejectedExecutionHandler)了。再后来因为疫情原因,公司能接到的项目也越来越少了,开发人员很多(Thread)已经没事儿可做了,大概过了两周时间(keepAliveTime),公司又为了节省开支就把这些空闲下来的非核心开发给开了。当然,核心开发也不是说一定不能动也是可以开的(allowCoreThreadTimeOut(true)),只不过肯定是优先考虑非核心人员。
有人说了,项目多的时候为啥不扩大公司规模呢?
首先,公司老板最多也就有养这几个员工的的能力,养的多了老板也吃不消,多招一个人可能也不会使工作效率提高,反而可能拖累其他开发的进度,能养几个员工也是经过老板深思熟虑加以往的经验总结得出的结果。




ThreadPoolExecutor 相比于其他创建线程池的优势在于,它可以通过参数来控制最大任务数和拒绝策略,让线程池的执行更加透明和可控,所以在阿里巴巴《Java开发手册》是这样规定的:

1
【强制要求】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。内存溢出的错误
1
2
3
4
5
说明:Executors 返回的线程池对象的弊端如下:

1) FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2)CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

总结

线程池的创建方式总共有以下 7 种:

  1. Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
  2. Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。
  4. Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。
  5. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。
  6. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
  7. ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。

线程池的创建推荐使用最后一种 ThreadPoolExecutor 的方式来创建,因为使用它可以明确线程池的运行规则,规避资源耗尽的风险

day24

1.基于接口和抽象类的匿名内部类的写法

1
2
3
4
5
6
7
8
9
10
11
abstract class Person {
public abstract void eat();
}
public static void main (String[] args) {
Person person = new Person () {
public void eat () {
sout();
}
};
person.eat();
}

2.接口和抽象类的区别

3.throw和throws区别
4.final和finally的区别
5.书写单例模式
6.序列化和反序列化的概念

今天的内容

1.Stream流

2.水果管理系统

1.Stream【难点】

Stream解决集合类库现有的弊端

先在有一个需求:

​ 将list集合中姓张的名字元素过滤到新的集合中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.qfedu.a_stream;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;

public class Demo1 {
public static void main(String[] args) {
//将list集合中姓张的名字元素过滤到新的集合中
List<String> list = new ArrayList<>();
list.add("张三");
list.add("张小三");
list.add("李四");
list.add("王五");
list.add("张大山");
//新建一个新的集合用来存姓张的元素
List<String> list1 = new ArrayList<>();
for (String s : list) {
if (s.startsWith("张")) {
list1.add(s);
}
}
System.out.println(list1);
System.out.println("=======");
//如果使用sytream流操作
//将集合对象转换成流对象
list.stream().filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));

list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("张");
}
}).forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
}
}

1.1获取流的对象

根据集合获取流,将集合中的数据变成流的形式

Collection接口下面的方法 streram();

1.根据List获取流对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.qfedu.a_stream;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class Demo2 {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("老邢");
strings.add("老邢xiao");
strings.add("xiao邢");
strings.add("xiao老邢");
//获取流对象
Stream<String> stream = strings.stream();
System.out.println(stream);
}
}

2.set也可以获取stream对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.qfedu.a_stream;

import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;

public class Demo3 {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("嘻嘻");
set.add("哈哈");
Stream<String> stream = set.stream();
System.out.println(stream);
}
}

3.根据map集合获取流对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.qfedu.a_stream;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

public class Demo4 {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("1", "狗蛋");
map.put("2", "腾飞");
//先获取键
Set<String> strings = map.keySet();
Stream<String> stream = strings.stream();
//获取值
Collection<String> values = map.values();
Stream<String> stream1 = values.stream();
//
Set<Map.Entry<String, String>> entries = map.entrySet();

Stream<Map.Entry<String, String>> stream2 = entries.stream();

}
}

1.2Stream流对象下面

流就是集合中的数据,方法对集合中进行操作的

方法的名称 方法的作用 方法种类 是否支持链式操作
count 统计个数 终结方法
forEach 逐个处理数据 终结方法
filter 过滤数据 函数的拼接
limit 取前几个 函数的拼接
skip 跳过前几个 函数的拼接
map 映射 函数的拼接
concat 拼接 函数的拼接

方法种类:

​ 终结方法:对流操作的时候,链式操作的时候一定是写在最后的

​ 函数拼接:方法还可以接着写方法

工厂的流水线:先造手机电池-》再造手机的主板-》造手机端额外壳-》一个成品

1.2.1count和forEach

count:统计流中的元素的个数

forEach:遍历数据的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.qfedu.a_stream;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class Demo5 {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("老邢");
strings.add("老邢xiao");
strings.add("xiao邢");
strings.add("xiao老邢");

long count = strings.stream().count();
System.out.println(count);//4
strings.stream().forEach(s->System.out.println(s));

}
}

1.2.2filter方法
Stream<T> filter(Predicate<? super T> predicate)返回由与此给定谓词匹配的此流的元素组成的流。
Predicate 是一个判断接口,咱们可以写一写返回值是boolean类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.qfedu.a_stream;

import java.util.ArrayList;
import java.util.List;

public class Demo6 {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("老邢");
strings.add("老邢xiao");
strings.add("xiao邢");
strings.add("xiao老邢");
//filter 看数据流,如果返回的是一个true 就把它留到流中。如果是一个false就把从流中踢出去
strings.stream().filter(s->s.endsWith("邢")).forEach(s-> System.out.println(s));

}
}

1.2.3limit

限制,取集合中前几个值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.qfedu.a_stream;

import java.util.ArrayList;
import java.util.List;

public class Demo7 {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("老邢");
strings.add("老邢xiao");
strings.add("骚磊");
strings.add("大骚磊");
strings.add("xiao邢");
strings.add("xiao老邢");
strings.add("小骚磊");
//找出前4个元素带有老的元素
//strings.stream().limit(4).forEach(s-> System.out.println(s));
strings.stream().limit(4).filter(s->s.contains("老")).forEach(s-> System.out.println(s));

}
}

1.2.4map方法

用来映射关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.qfedu.a_stream;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;

public class Demo8 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("11");
list.add("12");
list.add("13");
list.add("14");
//将集合中集合中字符串转为int类型的数据打印
// for (String s : list) {
// System.out.println(Integer.parseInt(s));
// }
list.stream().map(s -> Integer.parseInt(s)).forEach(s-> System.out.println(s));
list.stream().map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
}).forEach(new Consumer<Integer>() {
@Override
public void accept(Integer s) {
System.out.println(s);
}
});
}
}

1.2.5skip

跳过前几个,取m面的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.qfedu.a_stream;

import java.util.ArrayList;
import java.util.List;

public class Demo9 {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("老邢");
strings.add("老邢xiao");
strings.add("骚磊");
strings.add("大骚磊");
strings.add("xiao邢");
strings.add("xiao老邢");
strings.add("小骚磊");
strings.stream().skip(3).forEach(s-> System.out.println(s));
}
}

1.2.6concat

合并两个流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.qfedu.a_stream;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class Demo10 {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("老邢");
strings.add("老邢xiao");
strings.add("骚磊");
List<String> strings1 = new ArrayList<>();
strings1.add("大骚磊");
strings1.add("xiao邢");
strings1.add("xiao老邢");
strings1.add("小骚磊");
//先变成流,然后再合并
Stream<String> stream = strings.stream();
Stream<String> stream1 = strings1.stream();
Stream<String> concat = Stream.concat(stream, stream1);
concat.forEach(s-> System.out.println(s));
}
}

1.3收集流

将流转为集合

toList();将流转为list集合

toSet();将流转为set集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.qfedu.a_stream;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Demo11 {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("老邢");
strings.add("老邢xiao");
strings.add("骚磊");
strings.add("骚磊");
Stream<String> stream = strings.stream();
List<String> collect = stream.collect(Collectors.toList());
//Set<String> collect1 = stream.collect(Collectors.toSet());
System.out.println(collect);
}
}

今天的内容

1.反射【重点】

1.反射

反射很重要,咱们以后进行封装的时候都有必要书写反射,让咱们的代码具有更强普适性

Java反射是在动态的获取类, 类属性, 类方法, 类构造方法等内部一些类的信息。

反射本质就是反着来。

平常获取类 new 一个对象,现在可以通过反射来获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.qfedu.a_reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Demo1 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//想创建一个对象,直接new
Person person = new Person();
//今天再学一种方式 使用反射的方式获取一个类的对象
//java执行原理:先编译 后执行
//编译之后的文件叫 Person.class 编
// 译之后所有东西(类的属性,类的构造方法,类的成年成员方法)
//都在person.class文件中,这个文件变成了一个类对象了
//personClasss里卖弄有类的所有的信息
Class<Person> personClass = Person.class;
//getConstructor获得无参构造方法
//Constructor 把无参构造方法封装给以一个对象
Constructor<Person> constructor = personClass.getConstructor(null);
//要无参构造干嘛?可以通过构造方法创建对象!!!
Person person1 = constructor.newInstance(null);


}
public static void test(Person p) {

}
public static void test(Class cls) {

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
从数据库里面查数据,但是查出来的数据要赋值一个类
new admin ();
new goods();
new order();
三个类对应这数据库里面这三张表
admin表:
name pwd
狗蛋 123
goods表:
name price
汽车 78.9
order:
name status
汽车 已付款
class BasweDao {
public List<T> query (Class cls) {
获取了name pwd price status
值已经获取出来
//得把这些值赋值给实体类
//Admin admin = new Admin();
//只能对admin赋值了,其他类不能赋值,这就不叫封装了,封装的目的是具有普适性
Constructor<T> cst = cls.getConstructor(null);
T t = cst.newInstance(null);

}
}
Admin.class
query(Admin.class);
query(Goods.class);
query(Order.class);

1.1获取Class对象

为啥要获取Class对象?因为有了class对象才能找关于类的属性,方法 , 构造方法

Java可以将.java文件编译成.class文件,这个.calss文件中包含了原始文件中的所有的信息

.class文件会被类加载器加载到虚拟机中,可以通过.class文件获取原始类中的所有的信息

随之产生一个Class类对象。将Person.class变成了一个Class对象了

获取一个类的Class对象有三种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.qfedu.b_class;

import com.qfedu.a_reflect.Person;

public class Demo1 {
public static void main(String[] args) throws ClassNotFoundException {
//第一种获取class对象方式
//com.qfedu.a_reflect.Person 类全限定名
Class<?> aClass = Class.forName("com.qfedu.a_reflect.Person");
System.out.println(aClass);
//第二种方式
Class<Person> personClass = Person.class;
System.out.println(personClass);
//第三种方式
Class<? extends Person> aClass1 = new Person().getClass();
System.out.println(aClass1);

}
}

为啥要获取一个类的字节码文件对象,因为我要获取字节码文件下面的 构造方法 方法 属性 等信息

1.2获取Constructor对象

和一个类中的构造方法有关系。构造方法类对象

Constructor<?>[] getConstructors()返回包含一个数组 Constructor对象反射由此表示Class的所有公共构造 方法对象。
Constructor<?>[] getDeclaredConstructors()返回一个反映 Constructor对象表示的类声明的所有 Constructor对象的数组
Constructor<T> getConstructor(Class<?>... parameterTypes)返回一个 Constructor对象,该对象反映 Constructor对象表示的类
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)返回一个 Constructor对象,该对象反映 Constructor对象表示的类或接口的指定 构造方法对象

以上的方法都是Class对象调用的

下面这个方法是Construct对象调用的

T newInstance(Object... initargs)使用此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.qfedu.c_constructor;

import com.qfedu.a_reflect.Person;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Demo1 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//1.获取一个类对应的class对象
Class<Person> personClass = Person.class;
//2.再拿class对象获取构造方法对象
Constructor<?>[] constructors = personClass.getConstructors();
System.out.println(constructors);//内存地址
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);

}
System.out.println("-========");
//3.获取一个类所有的构造方法对象
Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
//4.获取单个的constructor对象
//参数是个null 就证明获取的是无参的
Constructor<Person> constructor = personClass.getConstructor(null);
System.out.println(constructor);//Person();
System.out.println("----------");
//5获取带有字符串类型的有参构造方法
//参数Class 对象 怎么将String转为Class对象
Constructor<Person> constructor1 = personClass.getConstructor(String.class);
System.out.println(constructor1);//Person(String name)
Constructor<Person> constructor2 = personClass.getConstructor(String.class, int.class);
System.out.println(constructor2);//Person(String name)
//不能的!!!
// Constructor<Person> constructor3 = personClass.getConstructor(int.class);
// System.out.println(constructor3);//Person(int name)
//6.获取私有化的构造方法
Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(int.class);
System.out.println(declaredConstructor);

//newInstance(Object... parameter) 是Constructor对象调用的
//不是calss对象调用的 通过构造方法对象实例化一个对象出来
Person person = constructor.newInstance(null);
System.out.println(person);
//如果有参的构造方法 有参构造方法可以传值
Person person1 = constructor1.newInstance("狗蛋");
System.out.println(person1);
Person person2 = constructor2.newInstance("老邢", 18);
System.out.println(person2);
}
}

只用getConstructor(Class…parameterType);

newInstance(Object… initargs);

一个类下面有构造方法 方法 属性

1.3获取Method对象

Method[] getMethods()获取当前类或者父类的公开的所有的方法
Method[] getDeclaredMethods()获取当前类所有的方法,但是不能获取父类的方案
Method getMethod(String name, 类<?>... parameterTypes)返回一个 方法对象,它反映此表示的类或接口的指定公共成
Method getDeclaredMethod(String name, 类<?>... parameterTypes)返回一个 方法对象,它反映此表示的类或接口的指定声明的方法 对象。

学一个Method下面的方法

Object invoke(Object obj, Object… args)在具有指定参数的 方法对象上调用此 方法对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.qfedu.d_method;

import com.qfedu.a_reflect.Person;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Demo1 {
public static void main(String[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Class<Person> personClass = Person.class;
//通过class对象获取一个类下面方法对象Method
//打印的本类公开的方法和父类的方法
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("==========");
//获取本类的所有的方法
Method[] declaredMethods = personClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {

System.out.println(declaredMethod);
}
//获取一个方法 第一个参数 String方法的名字,第二个参数 方法的参数的数据类型
Method eat = personClass.getMethod("eat", null);
System.out.println(eat);//无参的eat方法
Method eat1 = personClass.getMethod("eat", String.class);
System.out.println(eat1);//有参的eat方法 参数string类型的数据

//私有话的方法getDeclaredMethod
Method sleep = personClass.getDeclaredMethod("sleep", null);
System.out.println(sleep);
//方法对象获取以后干嘛?
//方法要被调用 使用Method对象调用invoke这个方法
//第一个Object 是你得告诉我在那个对象下面调用

//Object... args 是方法的参数的实参!!!
//invoke(Object obj, Object... args)在具有指定参数的 `方法`对象上调用此 `方法`对象
Person person = personClass.newInstance();
//方法对象自己执行方法
eat.invoke(person, null);
eat1.invoke(person, "狗子");

//证明权限不够!!!咋办 接近不了
//can not access a member
//咋办,让它接近就可以啦。 暴力反射
sleep.setAccessible(true);
sleep.invoke(person, null);
}
}

总结:

1
2
3
getMethod(string name, class<?> ... parameterType);
调用
invoke(Object obj, Object....paramter)

上午的内容

1
2
3
4
5
6
7
8
9
10
1.获取三种方式
Class.forName("包名+ 类名");
类.class
对象.getClass()
2.获取构造方法对象
class对象.getConstructor();
construct对象.newInstance();根据构造方法实例化对象
3.获取方法对象
class对象.getMethod(String name, Class<?> ...parameterType);
method对象.invoke(Object obj, Object... initargs);

类下面还有成员变量

Field

1.4获取Field对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.qfedu.e_field;

import com.qfedu.a_reflect.Person;

import java.lang.reflect.Field;

public class Demo1 {
public static void main(String[] args) throws NoSuchFieldException, InstantiationException, IllegalAccessException {
//属性是在一个类下面,也要Class对象获取属性对象
Class<Person> personClass = Person.class;
//getFields获取公开的属性
Field[] fields = personClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("===========");
Field[] declaredFields = personClass.getDeclaredFields();
for (Field declaredField : declaredFields) {

System.out.println(declaredField);
}
//获取一个属性对象 参数是属性的名字
Field name = personClass.getField("name");
System.out.println(name);
//能不能使用getField方法获取私有的属性或者默认的属性?不能!!
// Field age = personClass.getField("age");
// System.out.println(age);
Field age = personClass.getDeclaredField("age");
System.out.println(age);
Field sex = personClass.getDeclaredField("sex");
System.out.println(sex);
//属性对象可以获取的,获取以后干嘛?属性是用来赋值的
//对象属性进行赋值 set方法
Person person = personClass.newInstance();
name.set(person, "狗子");
System.out.println(person);
//暴力反射
age.setAccessible(true);
age.set(person, 12);
System.out.println(person);

}
}

回顾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1.三种创建Class对象的形式
Class.forName("")
类.class
对象.getCalss()
字节文件.class对象
2.Class下面获取构造方法对象的方法
getConstructors();
getDeclaredConstructors();
getConstructor(Class ..... parameterType);
getDeclaredConstructor(Class ..... parameterType);
3.Class下面获取方法对象的方法
getMethods();
getDeclaredMethods();
getMethod(String name, Class .... parameterType);
getDeclaredMethod(String name, Class .... parameterType);
4.Class下面获取属性对象的方法
getFields();
getDeclaredFields();
getField(String name);
getDeclaredField(String name);
5.Constructor对象下面的方法
newInstance(); 通过构造方法创建了实例对象
setAccessible(true);
6.Method对象下面的方法
setAccessible(true);
invoke(Object obj, Object ... param);
7.Field对象下面的方法
setAccessible(true);
set(Object obj, Object value);
8.简单聊聊对反射的理解

今天的内容

1.单例模式

2.序列化操作(IO流)

以下两个是jdk的新特性:

3.lambda表达式

4.Stream流

1.单例模式

Java提供了二十多种设计模式,在咱们教学过程中抽重要的学习,开发中用的

模式并不是一朝一夕学会的东西,工厂模式, 代理模式 策略模式等

设计模式是全球公认的。为了让咱们的代码变得更加简洁,效率更高,产生出来的模式

修改的时候更加方便

单例模式:

​ 要求在整个程序运行过程中,只出现一个实例对象。减少内存的消耗

​ 如何判断两个对象或者多个对象是否是单例,看内存地址。如果内存地址相等的话,绝对是同一个对象。

想一些问题:

​ 创建对象 通过关键字 new 来创建,但是new一次 再new一次对象的内存地址绝对不一样的。就意味着你必须将一个类的构造方法私有化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.qfedu.f_singleinstance;

class SingDog {
private static SingDog singDog = null;
private SingDog () {

}
public static SingDog getInstance() {

synchronized (SingDog.class) {
if (singDog == null) {
//
singDog = new SingDog();
}
}
return singDog;
}


}
class MyThread1 implements Runnable {
@Override
public void run() {
SingDog instance = SingDog.getInstance();
System.out.println(instance);
}
}
class MyThread2 implements Runnable {
@Override
public void run() {
SingDog instance = SingDog.getInstance();
System.out.println(instance);
}
}

public class Demo1 {
public static void main(String[] args) {

//以上两个对象是两个不同的对象。单例模式的目的
//是只能有一个对象,不能有多个对象,就意味着不能再new
//为啥?new一个就是对象。不能new 在类里面咋写?
//解决方案: 私有话构造方法
//不能new 想得到对象? 在类中定义一个静态方法
//为啥是静态方法 只能用来调用
// SingDog singdog = SingDog.getInstance();
//
// System.out.println(singdog);
// SingDog singdog1 = SingDog.getInstance();
// System.out.println(singdog1);
new Thread(new MyThread1()).start();
new Thread(new MyThread2()).start();

}
}

懒汉式的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.qfedu.a_single;

//懒汉式写法
class Person {
private static Person person;
private Person () {

}
public static synchronized Person getInstance () {
if (person == null) {
person = new Person();
}
return person;
}
}

饿汉式写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.qfedu.a_single;

class Cat {
private static final Cat cat = new Cat();
private Cat() {

}
public static Cat getIntance () {
return cat;
}
}
public class Demo3 {
public static void main(String[] args) {
Cat intance = Cat.getIntance();
Cat intance1 = Cat.getIntance();
System.out.println(intance);
System.out.println(intance1);

}

}

懒汉式:线程不安全

饿汉式:线程安全的

效率来说: 饿汉式效率高,因为不用加锁

性能来说: 饿汉式类一加载就实力出来对象。但是懒汉式调用方法的时候才进行创建对象

懒汉式的内存消耗是小于饿汉式的

2.序列化

和IO流有关

类 ObjectInputStream(反序列化) 和 ObjectOutputStream(序列化) 是高层次的数据流,它们包含反序列化和序列化对象的方法。

ObjectOutputStream 类包含很多写方法来写各种数据类型,但是一个特别的方法例外:

public final void writeObject(Object x) throws IOException

上面的方法序列化一个对象,并将它发送到输出流。相似的 ObjectInputStream 类包含如下反序列化一个对象的方法:

public final Object readObject() throws IOException, ClassNotFoundException

该方法从流中取出下一个对象,并将对象反序列化。它的返回值为Object,因此,你需要将它转换成合适的数据类型。

总结:序列化能干嘛? 将一个类对象信息(构造方法,属性,方法)可以写到本地一个文件中。这叫序列化。从文件中读取一个对象的信息,这叫反序列化。

序列化注意事项:

​ 1.被序列化的实体类必须实现一个接口 Serializable,不然不能被序列化

序列化案例:

将一个对象赋值完以后写到到本地。ObjectOutputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.qfedu.b_serialize;

import java.io.*;
import java.util.ArrayList;

public class Demo1 {
public static void main(String[] args) throws Exception {
//序列化:将对象存到一个文件中
Employee employee = new Employee();
employee.name = "骚磊";
employee.address = "月球";
employee.number = 100;
employee.mailCheck();
//将employee对象进行序列化 存到一个文件中 序列化的文件的后缀都是.ser
FileOutputStream fos = new FileOutputStream(new File("c:/aaa/emp.ser"));
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fos);
objectOutputStream.writeObject(employee);
objectOutputStream.close();
fos.close();
System.out.println("写入成功");


}
}

反序列化

将本地的文件信息(被序列化过的)写到一个对象中

ObjectInputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.qfedu.b_serialize;

import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class Demo2 {
public static void main(String[] args) throws Exception{
//进行反序列化
FileInputStream fis = new FileInputStream(new File("c:/aaa/emp1.ser"));
ObjectInputStream ois = new ObjectInputStream(fis);
Employee emp = (Employee)ois.readObject();
System.out.println(emp.name);
System.out.println(emp.number);
System.out.println(emp.address);
System.out.println(emp.sb);
emp.mailCheck();

}
}

总结:序列化将对象的值存到本地磁盘的文件中以作备份。反序列化可以将本次磁盘序列化过的文件读取到实体类的对象中。

3.Lambda表达式

Lambda表达式被称称为闭包。他是推动了Java8新特性的重要的一步。

Lambda表达式运行函数式编程。就是简化代码的。变得更加简洁,但是可读性特别差

3.1入门案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.qfedu.c_lambda;

public class Demo1 {
public static void main(String[] args) {
//新建一个线程
//第一种的创建方式
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("简洁嘻嘻哒" + i);
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("锤人" + i);
}
}
}).start();

//Thread构造方法是接口对象
Runnable runnable = ()-> System.out.println("嘻嘻");
new Thread(runnable).start();

}
}

3.2lambda表达式语法格式

只看接口下面的唯一的一个抽象方法。

1
2
3
4
接口  接口对象 =  ()->表达式;  无参 无返回值的
接口 接口对象 = (parameter)->表达式; 有参 无返回值的
接口 接口对象 = ()->{表达式;}; 无参 有返回值的
接口 接口对象 = (parameter)->{表达式;}; 有参有返回值
3.2.1无参无返回值的形式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.qfedu.c_lambda;

interface Computer {
void coding();
}
public class Demo4 {
public static void main(String[] args) {
//无参无返回值的
test(() -> System.out.println("敲代码"));
}
//而如果一个方法的参数是一个接口对象的话,难免要new 这个接口 重写方法

public static void test (Computer c) {
c.coding();
}
}

3.2.2有参无返返回值的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.qfedu.c_lambda;

interface C {
void eat(String name, int a);
}
public class Demo5 {
public static void main(String[] args) {
test(( name, a) -> System.out.println(name + "吃" + a) , "狗蛋", 4);
}
public static void test (C c, String name, int a) {
c.eat(name , a);
}
}

3.2.3无参有返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.qfedu.c_lambda;

interface D {
int num();
}
public class Demo6 {
public static void main(String[] args) {
test(() ->{
if (true) {
System.out.println("xixida");
}
return 500;
});
}
public static void test (D d) {
d.num();
}
}

3.2.4有参有返回值的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.qfedu.c_lambda;

interface E {
int add(int a, int b);
}
public class Demo7 {
public static void main(String[] args) {
test((a, b) -> a + b, 2, 3);

}
public static void test (E e, int a, int b) {
int sum = e.add(a, b);
System.out.println(sum);
}
}

day25

第二阶段 JavaWeb

数据库

前端(H5,CSS,JS,JQ,Ajax,Bootstrap)

服务器Tomcat

服务器技术Servlet

数据库概念

数据库(==DataBase,DB==):指长期保存在计算机的存储设备上,按照一定规则组织起来,可以被各种用户或应用共享的数据集合。

数据库管理系统(DataBase Management System,==DBMS==):指一种操作和管理数据库的大型软件,用于建立、使用和维护数据库,对数据库进行统一管理和控制,以保证数据库的安全性和完整性。用户通过数据库管理系统访问数据库中的数据。

==RDBMS==

数据库:存储、维护和管理数据的集合。

常见数据库

mysql,sqlserver,Oracle,db2 ===> R(Relation)DBMS ==> 关系型数据库

redis,MongoDB,HBase ==> NOSql ==> Not Only Sql

MySQL介绍

1
MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。

image-20220815095840069

MySQL卸载

  • 开始-控制面板-卸载程序-mysql

​ 如果找到,右键卸载

  • 删除隐藏文件

​ C:\ProgramData\MySQL

  • 删除MySQL文件夹

​ 删除MySQL的安装文件

  • 删除注册表

打开注册表 windos+r 输入 regedit

\HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\services\eventlog\Application\MySQL

\HKEY_LOCAL_MACHINE\SYSTEM\ControlSet002\services\eventlog\Application\MySQL

将最后一个MySQL文件删除

image-20220815100528760

MySQL安装

mysql现在安装的是5.7.

mysql的安装方式有两种:

一种是exe方式

另外一种解压版

这次就使用解压版安装


1 解压缩到非中文目录

image-20220815101235392

2 编写配置文件

2.1 在安装目录下新建my.ini的配置文件

打开文件后缀和隐藏文件显示

image-20220815101352491

image-20220815101429202

2.2 新建文件内编写内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Client]
port = 3306

[mysqld]
#设置3306端口
port = 3306
# 设置mysql的安装目录
basedir=E:\mysql-5.7.20-winx64
# 设置mysql数据库的数据的存放目录
datadir=E:\mysql-5.7.20-winx64\data
# 允许最大连接数
max_connections=200
# 服务端使用的字符集默认为8比特编码的latin1字符集
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB

[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8

特别说明:

basedir 要换成自己的路径

datadir 要换成自己的路径,且该data文件夹不能存在,因为后续会自动创建.现在存在反而后续会初始化失败!!

3 执行命令初始化

3.1 使用管理员权限打开命令提示符(CMD)

image-20220815101855345

3.2 进入到mysql的安装路径的bin目录下

image-20220815102056294

3.3 执行命令

安装服务

1
2
3
mysqld --install  //  显示服务安装成功后继续执行命令

Service successfully installed // 成功了
1
mysqld --initialize --console  // 初始化,成功后有一个临时密码 0.X*8Ym7AlH2

image-20220815102430355

4 开启服务

执行命令,开启mysql服务

1
net start mysql

image-20220815102630568

5 修改密码

登录到mysql

1
2
mysql -uroot -p
Enter password: 0.X*8Ym7AlH2

image-20220815102744623

修改密码

1
set password = password('123456');

image-20220815102811473

退出再试

安装BUG

1 报错120.dll

image-20220815112711058

下载安装

image-20220815112743401

双击安装后,重新执行命令

2 没有服务名

缺失一个步骤,需要执行命令 mysqld –install,后再开启服务

3 初始化失败

执行完mysqld –initialize –console 后,没有出现密码

  1. 确定my.ini配置文件内容是否正确
  2. 安装路径内是否已经存在data文件夹,如果存在的话删除data文件再试

4 万能解决方案

1 认真,慢

2 检查my.ini内容是否正确

3 管理员权限进入cmd

4 执行命令先删除服务 mysqld remove

5 再重新安装服务器mysqld --install

6 在重新初始化产生密码

7 启动服务

….

配置环境变量

选择我的电脑–>右键属性–>高绩系统设置–>系统环境变量

image-20220815113727201

image-20220815113746981

image-20220815113841015

image-20220815113935206

image-20220815114055811

客户端基本操作

  • 启动服务

    • 命令启动 net start mysql

    • 图形化启动

      image-20220815142230814

    • 现在,一般情况下,开机自启的.

  • 登录mysql

    • 使用命令行登录 mysql -uroot -p123456
    • Navicat连接
  • 退出

    • exit

SQL语言

SQL:Structure Query Language。(结构化查询语言)

SQL被美国国家标准局(ANSI)确定为关系型数据库语言的美国标准,后来被国际化标准组织(ISO)采纳为关系数据库语言的国际标准。现在遵循的SQL99标准

各数据库厂商都支持ISO的SQL标准。普通话

各数据库厂商在标准的基础上做了自己的扩展。方言


DDL(Data Definition Language):数据定义语言,用来定义数据库对象:库、表、列等;

DML(Data Manipulation Language):数据操作语言,用来定义数据库记录(数据-增删改);

*DQL()**(Data Query Language):数据查询语言,用来查询记录(数据)。

DCL(Data Control Language):数据控制语言,用来定义访问权限和安全级别;

注意: ==sql语句以**;**==结尾(偶尔见到有\g结尾 , \G 也可以…)

DDL

DDL主要是针对数据库,表,列的操作,即创建,查询,修改,删除

查询数据库

1
show databases;	

创建数据库

1
create database 库名;

修改

1
2
3
创建数据库时,默认字符集是utf8.
修改命令只能修改字符集,不能修改数据库名.
alter database ....

删除数据库

1
drop database 库名;

操作表的前提是,得先进入某个数据库,才能操作数据库内部的表

选择/进入数据库

1
use 库名;

查询当前所在库

1
select database();

image-20220815145042363

查询当前库下所有表

1
show tables;

创建表

1
2
3
4
5
create table 表名(
字段1 数据类型(长度),
字段2 数据类型(长度),
...
);

常用数据类型

数据类型 解释 长度
int 整型 11,可以不写,默认11
varchar 字符串 0-255
char 字符串 0-255
double 浮点型 (5,2)总长5位,其中包含两位小数,999.99
date 日期 没有长度
datetime 日期时间 没有长度
timestamp 时间戳 没有长度

数据库中,字符串不分单字符,所有字符串都是==单引号括==.使用char和varchar都可以代表字符串

varchar和char的区别

var是variable的缩写,就是可变化的字符串,例如varchar(10), 存入’张三’

char是定长,例如char(10),存入’张三 ‘

需求:

创建stu表,有字段学号整型,姓名字符型,性别字符型,年龄整型,生日日期型

1
2
3
4
5
6
7
mysql> create table t_stu (
-> sid int(11),
-> name varchar(20),
-> sex char(1),
-> age int,
-> birthday date
-> );

image-20220815151626031

修改表名

1
rename table 旧名 to 新名;

修改列名

1
alter table 表名 change 旧名 新名 数据类型(长度)

查看表结构

1
desc 表名;

加一列

1
alter table 表名 add 新列名 数据类型(长度);

删列

1
alter table 表名 drop 列名;

删表

1
drop table 表名;

总结

创建–> create database/table

修改 –> alter database/table

删除–> drop database/table

查询 –> show databases/tables

图形化界面

SQLYog

Navicat

DBeaver


image-20220815161253508

image-20220815161440343

image-20220815161546701

image-20220815161929668

image-20220815162114748

image-20220815162241702

image-20220815162400393

image-20220815162609999

DML

数据操作语言,针对表中数据进行操作,主要是对表数据进行增,删,改。

增加(insert)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
语法
insert into 表名(字段1,字段2,...) values (值1,值2,...);
其他用法:
1 当要插入全部列时,表名后的列名可以省略
此时要注意,值的顺序要与原表的顺序一致
2 可以指定部分列,插入数据
列和值的顺序,数量,数据类型要一一对应
3 字符串使用单引号
4 日期,暂时也是使用单引号括起
5 可以支持同时插入多条数据
values后用()分别写多条数据,用逗号隔开
*/
-- 标准语法
insert into stu (id,name,age,sex,birthday)
values (1,'张三丰',130,'男','1890-01-01');

-- 省略
insert into stu values(2,'张无忌',18,'男','1890-01-01')
-- 插入部分数据
insert into stu (id,name) values (3,'赵敏');
insert into stu (id,name) values (4,6); -- 可以,因为mysql支持类型转换,单不建议
insert into stu (id,name) values ('5',6);-- 可以,因为mysql支持类型转换,单不建议
-- 日期类型,支持yyyy-MM-dd或yyyy/MM/dd
insert into stu values(7,'武器大师',18,'男','1990/01/01')
-- 插入多条数据
insert into stu values
(8,'狗头',18,'公','1990/01/01'),
(9,'雷克顿',19,'公','1991/01/01'),
(10,'阿兹尔',20,'男','1981/01/01');

修改(update)

1
2
3
4
5
6
7
8
/*
语法:
update 表名 set 字段1=值1[,字段2=值2,...] [where 字段 = 值];
*/
-- 标准
update stu set age = 25,name = '曾阿牛' where id = 2;
-- where条件虽说可加可不加.但是不加的话将影响全部数据.
-- 所以,强烈建议!!!以后更新必须加条件。

删除(delete)

1
2
3
4
5
6
7
8
/*
语法:
delete from 表名 [where 字段 = 值];
从表中删除数据,强烈建议加条件,否则会删除全部数据!!!!
公司实际开发,一般不会真的删除数据,
会在表中设计[状态]字段,删除其实是在执行update,更新状态而已.
*/
delete from stu where id = 10;