我本无心入江南,奈何江南入我心
一、时间日期
1. data
⑴. 时间分类
- 世界标准时间: 格林尼治时间 / 格林威治时间(Greenwich Mean Time)简称 GMT
- 中国的标准时间: 世界标准时间 + 8小时
- 计算机中的起始时间: 1970年1月1日 00:00:00
1969年8月,贝尔实验室的程序员肯汤普逊利用妻儿离开一个月的机会,开始着手创造一个全新的革命性的操作系统,他使用B编译语言在老旧的PDP-7机器上开发出了Unix的一个版本。 随后,汤普逊和同事丹尼斯里奇改进了B语言,开发出了C语言,重写了UNIX。 1970年1月1日 算C语言的生日
⑵. 构造方法和常用方法
方法名 | 描述 |
---|---|
public Date() | 阿创建一个Date对象,表示默认时间发 |
public Date(long date) | 阿创建一个Date对象,表示指定时间发 |
public long getTime() | 获取时间对象的毫秒值 |
public void setTime(long time) | 设置时间,传递毫秒值 |
示例:
public class test01 {
public static void main(String[] args) {
// 电脑当前时间
Date date = new Date();
System.out.println("date => " + date);
// date => Fri Jul 08 19:55:05 CST 2022
// 计算机时间原点
Date date1 = new Date(0L);
System.out.println("date1 => " + date1);
// date1 => Thu Jan 01 08:00:00 CST 1970
// 指定时间 (计算机原点时间 + 9 个小时)
Date date2 = new Date(60L * 60 * 1000);
System.out.println("date2 =>" + date2);
// 系统当前时间(毫秒数)
long date3 = System.currentTimeMillis();
System.out.println(date3);
// => 1657286218008
// getTime
Date date4 = new Date();
long time1 = date4.getTime();
System.out.println(time1);
// => 1657286218008
// setTime
Date date5 = new Date();
date5.setTime(0L);
System.out.println(date5);
// => Thu Jan 01 08:00:00 CST 1970
}
}
2. SimpleDateFormat
方法名 | 描述 |
---|---|
public SimpleDateFormat () | 构造一个SimpleDateFormat,使用默认格式 |
public SimpleDateFormat (String pattern) | 构造一个SimpleDateFormat,使用指定的格式 |
public final String format(Date date) | 把时间按照固定格式进行展示 |
示例:
public class test01 {
public static void main(String[] args) throws ParseException {
// 格式化:将日期格式化成日期/时间字符串
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年-MM月-dd日 HH:mm:ss");
String time1 = sdf.format(date);
System.out.println(time1);
// => 2022年-07月-08日 21:30:47
// 解析:从给定字符串的开始解析文本以生成日期
String time2 = "2023-02-27";
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd");
Date date2 = sdf2.parse(time2);
System.out.println(date2);
// => Mon Feb 27 00:00:00 CST 2023
}
}
3. 实例
需求: 活动时间是 2022年11月11日 0:0:0 ~ 2022年11月11日 10:00:00,zoe当天8:03下单,tony当天10:21下单,请问谁成功了,用毫秒值进行分析
public class test {
public static void main(String[] args) throws ParseException {
String zoe = "2022年11月11日 8:03:00";
String tony = "2022年11月11日 10:21:00";
secKill(zoe);
secKill(tony);
}
public static void secKill(String time) throws ParseException {
String start = "2022年11月11日 0:0:0";
String end = "2022年11月11日 10:00:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
long startTime = sdf.parse(start).getTime();
long endTime = sdf.parse(end).getTime();
// System.out.println(startTime);
// => 1668096000000
// System.out.println(endTime);
// => 1668132000000
long timeDate = sdf.parse(time).getTime();
if(timeDate > endTime || timeDate < startTime) {
System.out.println("不好意思,未能秒杀成功");
} else {
System.out.println("恭喜你,秒杀成功了");
}
}
二、集合 List
1. 概述
- 数组的长度是不可变的,集合的长度是可变的
- 数组可以存基本数据类型和引用数据类型
- 集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类
示例:
public class test1 {
public static void main(String[] args) {
// 数组可以储存基础数据类型,也可以储存引用数据类型
int[] arr1 = {1, 2, 3};
String[] arr2 = {"a", "b", "c"};
System.out.println(Arrays.toString(arr1));
// => [1, 2, 3]
System.out.println(Arrays.toString(arr2));
// => [a, b, c]
// 如果集合要储存基础数据类型,那么储存的是他们的包装类
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
list1.add(3);
System.out.println(list1);
// => [1, 2, 3]
}
}
继承体系:
2. Collection集合
⑴. 概述
- Collection集合概述:
- 是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
- JDK 不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现
- 创建Collection集合的对象:
- 多态的方式
- 具体的实现类ArrayList
⑵. 常用方法
方法名 | 描述 |
---|---|
boolean add(E e) | 添加元素 |
boolean remove(Object o) | 从集合中移除指定的元素 |
boolean removeif(Object o) | 根据条件进行删除 |
void clear() | 清空集合 |
boolean contains(Object o) | 判断集合中是否存在指定的元素 |
boolean isEmpty() | 判断集合是否为空 |
示例:
public class demo1 {
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("aa");
collection.add("bbb");
collection.add("cccc");
System.out.println(collection);
// => [aa, bbb, cccc]
// boolean remove(Object o) 从集合中移除指定的元素
boolean res1 = collection.remove("aa");
System.out.println(collection);
// => [bbb, cccc]
// boolean removeif(Object o) 根据条件进行删除
collection.add("ddddd");
collection.add("eeeeee");
collection.removeIf((String str) -> {
return str.length() == 5;
});
System.out.println(collection);
// => [bbb, cccc, eeeeee]
// void clear() 清空集合
collection.clear();
System.out.println(collection);
// => []
// boolean contains(Object o) 判断集合中是否存在指定的元素
collection.add("a");
collection.add("bb");
collection.add("ccc");
collection.add("dddd");
boolean result2 = collection.contains("ccc");
System.out.println(result2);
// => true
// boolean isEmpty() 判断集合是否为空
boolean result3 = collection.isEmpty();
System.out.println(result3);
// => false
// int size() 集合的长度,也就是集合中元素的个数
int result4 = collection.size();
System.out.println(result4);
// => 4
}
}
⑶. 集合遍历
Iterator
:迭代器,集合的专用遍历方式Iterator<E> iterator()
:返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引- Iterator中的常用方法:
boolean hasNext()
:判断当前位置是否有元素可以被取出E next()
:获取当前位置的元素,将迭代器对象移向下一个索引位置
示例:
public class demo3 {
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("a");
collection.add("b");
collection.add("c");
collection.add("d");
// 迭代器
// 迭代器对象一旦被创建出来,默认指向集合的0索引处
Iterator it = collection.iterator();
System.out.println(it.hasNext());
// => true
// System.out.println(it.next());
// 取出当前位置的元素 + 将迭代器往后移动一个索引的位置
// => a
// System.out.println(it.next());
// => b
// System.out.println(it.next());
// => c
// System.out.println(it.next());
// => d
// System.out.println(it.next());
// => NoSuchElementException
Collection<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
Iterator it2 = list.iterator();
while (it2.hasNext()) {
System.out.print(it2.next() + ",");
}
// => aa,bb,cc,
}
}
⑷. 增强 for
- 简化数组和Collection集合的遍历
- 它是JDK5之后出现的,其内部原理是一个Iterator迭代器
- 实现Iterable接口的类才可以使用迭代器和增强for
格式定义:
// for(元素数据类型 变量名 : 数组或者Collection集合) {
// 在此处使用变量即可,该变量就是元素
// }
ArrayList<String> list = new ArrayList<>();
// 添加一些元素
for(String s : list) {
System.out.println(i);
}
示例:
public class demo4 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
for (String s: list) {
System.out.println(s);
}
}
}
⑸. 三种循环的使用场景
- 如果需要操作索引,使用普通for循环
- 如果在遍历的过程中需要删除元素,请使用迭代器
- 如果仅仅想遍历,那么使用增强for
示例:
public class demo1 {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("a");
list.add("bb");
list.add("ccc");
// for 循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 加强for
for (String s : list) {
System.out.println(s);
}
// 迭代器
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
⑹. 实例
需求: 创建一个Collection集合存储学生对象的集合,存储3个学生对象,使用程序实现在控制台遍历该集合
public class demo1 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// void add(int index,E element) 在此集合中的指定位置插入指定的元素
list.add("bb");
list.add("ccc");
list.add(0, "a");
System.out.println(list);
// => [a, bb, ccc]
// E remove(int index) 删除指定索引处的元素,返回被删除的元素
String s = list.remove(1);
System.out.println(s);
// => bb
System.out.println(list);
// => [a, ccc]
// E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
String s2 = list.set(1, "dddd");
System.out.println(s2);
// => ccc
System.out.println(list);
// => [a, dddd]
// E get(int index) 返回指定索引处的元素
String s3 = list.get(0);
System.out.println(s3);
// => a
}
}
3. List集合
⑴. 概述
- List集合概述:
- 有序集合,这里的有序指的是存取顺序
- 用户可以精确控制列表中每个元素的插入位置;用户可以通过整数索引访问元素,并搜索列表中的元素
- 与
Set
集合不同,列表通常允许重复的元素
- List集合特点:
- 有序: 存储和取出的元素顺序一致
- 有索引: 可以通过索引操作元素
- 可重复: 存储的元素可以重复
⑵. 特有方法
方法名 | 描述 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
示例:
public class demo1 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// void add(int index,E element) 在此集合中的指定位置插入指定的元素
list.add("bb");
list.add("ccc");
list.add(0, "a");
System.out.println(list);
// => [a, bb, ccc]
// E remove(int index) 删除指定索引处的元素,返回被删除的元素
String s = list.remove(1);
System.out.println(s);
// => bb
System.out.println(list);
// => [a, ccc]
// E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
String s2 = list.set(1, "dddd");
System.out.println(s2);
// => ccc
System.out.println(list);
// => [a, dddd]
// E get(int index) 返回指定索引处的元素
String s3 = list.get(0);
System.out.println(s3);
// => a
}
}
4. ArrayList集合
数据结构是计算机存储、组织数据的方式,是指相互之间存在一种或多种特定关系的数据元素的集合
一个程序 = 算法 + 数据结构,数据结构是实现算法的基础,选择合适的数据结构可以带来更高的运行或者存储效率
数据元素相互之间的关系称为结构,根据数据元素之间关系的不同特性,通常有如下四类基本的结构:
- 集合结构: 该结构的数据元素间的关系是“属于同一个集合”
- 线性结构: 该结构的数据元素之间存在着一对一的关系
- 树型结构: 该结构的数据元素之间存在着一对多的关系
- 图形结构: 该结构的数据元素之间存在着多对多的关系,也称网状结构
由于数据结构种类太多,逻辑结构可以再分成为:
- 线性结构: 有序数据元素的集合,其中数据元素之间的关系是一对一的关系,除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的
- 非线性结构: 各个数据元素不再保持在一个线性序列中,每个数据元素可能与零个或者多个其他数据元素发生关联
1. 数组
在程序设计中,为了处理方便, 一般情况把具有相同类型的若干变量按有序的形式组织起来,这些按序排列的同类数据元素的集合称为数组
2. 栈
一种特殊的线性表,只能在某一端插入和删除的特殊线性表,按照先进后出的特性存储数据
先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据
3. 队列
跟栈基本一致,也是一种特殊的线性表,其特性是先进先出,只允许在表的前端进行删除操作,而在表的后端进行插入操作
4. 链表
是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成
一般情况,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针
5. 树
树是典型的非线性结构,在树的结构中,有且仅有一个根结点,该结点没有前驱结点。在树结构中的其他结点都有且仅有一个前驱结点,而且可以有两个以上的后继结点
6. 图
一种非线性结构。在图结结构中,数据结点一般称为顶点,而边是顶点的有序偶对。如果两个顶点之间存在一条边,那么就表示这两个顶点具有相邻关系
7. 堆
堆是一种特殊的树形数据结构,每个结点都有一个值,特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆
8. 散列表
若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上,不需比较便可直接取得所查记录
5. LinkedList集合
⑴. 概述
- List集合常用子类:
- ArrayList: 底层数据结构是数组,查询快,增删慢
- LinkedList: 底层数据结构是链表,查询慢,增删快
⑵. 特有方法
方法名 | 描述 |
---|---|
public void addFirst (E e) | 在该列表开头插入指定的元素 |
public void addLast (E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst () | 返回此列表中的第一个元素 |
public E getLast () | 返回此列表中的最后一个元素 |
public E removeFirst () | 从此列表中删除并返回第一个元素 |
public E removeLast () | 从此列表中删除并返回最后一个元素 |
示例:
public class demo3 {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("a");
list.add("bb");
list.add("ccc");
// public void addFirst(E e) 在该列表开头插入指定的元素
list.addFirst("first");
System.out.println(list);
// => [first, a, bb, ccc]
// public void addLast(E e) 将指定的元素追加到此列表的末尾
list.addLast("last");
System.out.println(list);
// => [first, a, bb, ccc, last]
// public E getFirst() 返回此列表中的第一个元素
String s1 = list.getFirst();
System.out.println(s1);
// => first
// public E getLast() 返回此列表中的最后一个元素
String s2 = list.getLast();
System.out.println(s2);
// => last
// public E removeFirst() 从此列表中删除并返回第一个元素
String s3 = list.removeFirst();
System.out.println(s3);
// => first
System.out.println(list);
// => [a, bb, ccc, last]
// public E removeLast() 从此列表中删除并返回最后一个元素
String s4 = list.removeLast();
System.out.println(s4);
// => last
System.out.println(list);
// =>[a, bb, ccc]
}
}
6. 泛型
是JDK5中引入的特性,它提供了编译时类型安全检测机制
- 泛型的好处:
- 把运行时期的问题提前到了编译期间
- 避免了强制类型转换
- 特点:
- 如果一个类的后面有
<E>
,表示这个类是一个泛型类 - 创建泛型类的对象时,必须要给这个泛型确定具体的数据类型
- 如果一个类的后面有
泛型类:
# 修饰符 class 类名<类型> { }
# 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
public class Generic<T> { }
泛型方法:
# 修饰符 <类型> 返回值类型 方法名(类型 变量名) { }
public <T> void show(T t) { }
泛型接口:
# 修饰符 interface 接口名<类型> { }
public interface Generic<T> { }
类型通配符:
- 类型通配符:
<?>
ArrayList<?>
:表示元素类型未知的ArrayList,它的元素可以匹配任何的类型- 但是并不能把元素添加到ArrayListList中了,获取出来的也是父类类型
- 类型通配符上限:
<? extends 类型>
- 比如:
ArrayListList <? extends Number>
:它表示的类型是Number或者其子类型类型通配符下限:<? super 类型> - 比如:
ArrayListList <? super Number>
:它表示的类型是Number或者其父类型
三、集合 Set
集合体系结构:
1. Set
Set集合特点:
- 可以去除重复
- 存取顺序不一致
- 没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取,删除Set集合里面的元素Set集合练习存储字符串并遍历
需求: 储存字符串并遍历
public class demo1 {
public static void main(String[] args) {
Set<String> set = new TreeSet<>();
set.add("ccc");
set.add("a");
set.add("a");
set.add("bb");
// set 集合没有索引,不能通过普通 for 循环遍历
// 迭代器
Iterator<String> it = set.iterator();
while (it.hasNext()) {
System.out.print(it.next() + ",");
}
// => a,bb,ccc,
// 增强 for
for (String s : set) {
System.out.print(s + ",");
}
// => a,bb,ccc,
}
}
2. TreeSet
⑴. 概述
TreeSet集合特点:
- 不包含重复元素的集合
- 没有带索引的方法
- 可以将元素按照规则进行排序
⑵. Comparable 自然排序
- 使用空参构造创建TreeSet集合
- 自定义的Student类实现Comparable接口
- 重写里面的compareTo方法
示例:
public class demo3 {
public static void main(String[] args) {
TreeSet<Student> students = new TreeSet<>();
Student s1 = new Student("zoe", 18);
Student s2 = new Student("tony", 65);
Student s3 = new Student("ami", 11);
Student s4 = new Student("skate", 11);
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
System.out.println(students);
// => [Student{name='ami', age=11}, Student{name='skate', age=11}, Student{name='zoe', age=18}, Student{name='tony', age=65}]
}
}
class Student implements Comparable<Student> {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 制定排序规则
@Override
public int compareTo(Student o) {
int result = this.age - o.age;
// 如果年龄相同,根据姓名进行排序(如果姓名也相同,不写入)
result = (result == 0 ? this.name.compareTo(o.getName()) : result);
return result;
}
}
⑶. Comparator 比较器排序
- TreeSet的带参构造方法使用的是比较器排序对元素进行排序的
- 比较器排序,就是让集合构造方法接收
Comparator
的实现类对象,重写compare(T o1,T o2)
方法 - 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
示例:
public class demo4 {
public static void main(String[] args) {
// TreeSet<Teacher> teachers = new TreeSet<>(new Comparator<Teacher>() {
// @Override
// // o1:要存入的元素、o2:集合中的元素
// public int compare(Teacher o1, Teacher o2) {
// int result = o1.getAge() - o2.getAge();
// result = (result == 0 ? o1.getName().compareTo(o2.getName()) : result);
// return result;
// }
// });
// 简化
TreeSet<Teacher> teachers = new TreeSet<>(
(Teacher o1, Teacher o2) -> {
int result = o1.getAge() - o2.getAge();
result = (result == 0 ? o1.getName().compareTo(o2.getName()) : result);
return result;
}
);
Teacher t1 = new Teacher("zoe", 18);
Teacher t2 = new Teacher("tony", 43);
Teacher t3 = new Teacher("ami", 12);
Teacher t4 = new Teacher("skate", 12);
teachers.add(t1);
teachers.add(t2);
teachers.add(t3);
teachers.add(t4);
System.out.println(teachers);
// => [Teacher{name='ami', age=12}, Teacher{name='skate', age=12}, Teacher{name='zoe', age=18}, Teacher{name='tony', age=43}]
}
}
class Teacher {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
public Teacher() {
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
⑷. 两者比较
- 自然排序: 自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序
- 比较器排序: 创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序
- 在使用的时候,默认使用自然排序,当自然排序不满足现在的需求时,使用比较器排序
- 两种方式中,关于返回值的规则:
- 如果返回值为负数,表示当前存入的元素是较小值,存左边
- 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
- 如果返回值为正数,表示当前存入的元素是较大值,存右边
3. HashSet
⑴. 概述
- HashSet集合特点:
- 底层数据结构是哈希表
- 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以元素唯一
示例:
public class demo1 {
public static void main(String[] args) {
HashSet<String> hs = new HashSet<>();
hs.add("a");
hs.add("ccc");
hs.add("bb");
hs.add("bb");
System.out.println(hs);
// => [bb, a, ccc]
// 迭代器
Iterator<String> it = hs.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
// => a bb ccc
// 增强 for
for (String h : hs) {
System.out.println(h);
}
// => a bb ccc
}
}
⑵. 哈希值
哈希值: 是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
# 获取对象的哈希值
public int hashCode():返回对象的哈希码值
对象的哈希值特点:
- 同一个对象多次调用
hashCode()
方法返回的哈希值是相同的 - 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
HashSet1.8版本原理解析:
- 底层结构: 哈希表(数组、链表、红黑树的结合体)
- 当挂在下面的元素过多,那么不利于查询,所以在JDK8以后,当链表长度超过8的时候,自动转换为红黑树,存储流程不变
⑶. 实例
需求: 创建储存对象的数据集合,如果属性相同即判定为一个对象,不再存入
public class demo3 {
public static void main(String[] args) {
HashSet<Student2> hs = new HashSet<>();
Student2 s1 = new Student2("zoe", 18);
Student2 s2 = new Student2("tony", 56);
Student2 s3 = new Student2("tony", 56);
hs.add(s1);
hs.add(s2);
hs.add(s3);
// 对象地址计算出来的 哈希值
System.out.println(s1.hashCode());
// => 3744322
System.out.println(s1.hashCode());
// => 3744322
System.out.println(s2.hashCode());
// => 110544754
System.out.println(hs);
// => [Student{name='tony', age=56}, Student{name='zoe', age=18}]
}
}
class Student2 {
private String name;
private int age;
public Student2(String name, int age) {
this.name = name;
this.age = age;
}
public Student2() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
// 重写之后是根据对象的属性值来计算哈希值,和对象的地址就没关系了
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student2 student2 = (Student2) o;
if (age != student2.age) return false;
// return name != null ? name.equals(student2.name) : student2.name == null;
return Objects.equals(name, student2.name);
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
四、集合 Map
1. Map
⑴. 概述
- Interface Map<K,V> K:键的数据类型;V:值的数据类型
- 键不能重复,值可以重复
- 键和值是一一对应的,每一个键只能找到自己对应的值
- (键 + 值) 这个整体 我们称之为“键值对”或者“键值对对象”,在Java中叫做“Entry对象”。
示例:
public class demo1 {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("zoe", 18);
map.put("tony", 53);
map.put("ami", 62);
System.out.println(map);
// => {tony=53, zoe=18, ami=62}
}
}
⑵. 常用方法
方法名 | 描述 |
---|---|
V put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
示例:
public class demo2 {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
// V put(K key,V value) 添加元素
map.put("zoe", 18);
map.put("tony", 56);
map.put("ami", 47);
map.put("jack", 87);
System.out.println(map);
// => {tony=56, zoe=18, ami=47, jack=87}
// 如果要添加的键是存在的,那么会覆盖原先的值,把原先值当做返回值进行返回。
int age1 = map.put("zoe", 34);
System.out.println(age1);
// => 18
System.out.println(map);
// => {tony=56, zoe=34, ami=47, jack=87}
// V remove(Object key) 根据键删除键值对元素
int age2 = map.remove("zoe");
System.out.println(age2);
// => 34
System.out.println(map);
// => {tony=56, ami=47, jack=87}
// void clear() 移除所有的键值对元素
// map.clear();
// System.out.println(map);
// => {}
// boolean containsKey(Object key) 判断集合是否包含指定的键
boolean result1 = map.containsKey("zoe");
System.out.println(result1);
// => false
// boolean isEmpty() 判断集合是否为空
boolean result2 = map.isEmpty();
System.out.println(result2);
// => false
// int size() 集合的长度,也就是集合中键值对的个数
int size = map.size();
System.out.println(size);
// => 3
}
}
⑶. 遍历 map 集合
方法名 | 描述 |
---|---|
V get(Object key) | 根据键获取值 |
Set keySet() | 获取所有键的集合 |
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 |
K getKey() | 获得键 |
V getValue() | 获得值 |
示例:
public class demo3 {
public static void main(String[] args) {
// 遍历 map 的键值对
Map<String, String> map = new HashMap<>();
map.put("1号键", "1号值");
map.put("2号键", "2号值");
map.put("3号键", "3号值");
map.put("4号键", "4号值");
map.put("5号键", "5号值");
System.out.println(map);
// => {4号键=4号值, 3号键=3号值, 2号键=2号值, 1号键=1号值, 5号键=5号值}
// 获取所有的键
Set<String> keys = map.keySet();
for (String key : keys) {
// 通过键 获取到对应的值
String value = map.get(key);
System.out.println(key + "---" + value);
// => 4号键---4号值
// => 3号键---3号值
// => 2号键---2号值
// => 1号键---1号值
// => 5号键---5号值
}
// 第二种遍历方式
// 获取所有的键值对对象
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
// 得到每一个键值对对象
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "---" + value);
// => 4号键---4号值
// => 3号键---3号值
// => 2号键---2号值
// => 1号键---1号值
// => 5号键---5号值
}
}
}
2. HashMap
⑴. 概述
- HashMap底层是哈希表结构的
- 依赖hashCode方法和equals方法保证键的唯一
- 如果键要存储的是自定义对象,需要重写hashCode和equals方法
特点:
- HashMap是Map里面的一个实现类
- 没有额外需要学习的特有方法,直接使用Map里面的方法就可以了
- HashMap跟HashSet一样底层是哈希表结构的
⑵. 实例
需求: 创建一个HashMap集合,键是学生对象(Student),值是籍贯(String)。存储三个键值对元素,并遍历
public class demo4 {
public static void main(String[] args) {
HashMap<Student, String> hm = new HashMap<>();
Student s1 = new Student("zoe", 18);
Student s2 = new Student("tony", 53);
Student s3 = new Student("ami", 37);
hm.put(s1, "北京");
hm.put(s2, "上海");
hm.put(s3, "武汉");
System.out.println(hm);
// => {Student{name='zoe', age=18}=北京, Student{name='tony', age=53}=上海, Student{name='ami', age=37}=武汉}
// 遍历 1:获取所有的键,再通过键找对应的值
Set<Student> keys = hm.keySet();
for (Student key : keys) {
String value = hm.get(key);
System.out.println(key + "---" + value);
// => Student{name='zoe', age=18}---北京
// => Student{name='tony', age=53}---上海
// => Student{name='ami', age=37}---武汉
}
// 遍历 2:先获取键值对对象,再获取里面的键和值
Set<Map.Entry<Student, String>> entries = hm.entrySet();
for (Map.Entry<Student, String> entry : entries) {
Student key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "---" + value);
// => Student{name='zoe', age=18}---北京
// => Student{name='tony', age=53}---上海
// => Student{name='ami', age=37}---武汉
}
// 遍历 3:forEach 方法
hm.forEach(
(Student key, String value) -> {
System.out.println(key + "---" + value);
}
);
// => Student{name='zoe', age=18}---北京
// => Student{name='tony', age=53}---上海
// => Student{name='ami', age=37}---武汉
}
}
class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void setAge(int age) {
this.age = age;
}
}
3. TreeMap
⑴. 概述
- TreeMap底层是红黑树结构的
- 依赖自然排序或者比较器排序,对键进行排序
- 如果键存储的是自定义对象,需要实现
Comparable
接口或者在创建TreeMap
对象时候给出比较器排序规则
特点:
- TreeMap是Map里面的一个实现类。
- 没有额外需要学习的特有方法,直接使用Map里面的方法就可以了
- TreeMap跟TreeSet一样底层是红黑树结构的
⑵. 实例
需求: 创建一个TreeMap集合,键是学生对象(Student),值是籍贯(String);学生属性姓名和年龄,按照年龄进行排序并遍历。
public class demo5 {
public static void main(String[] args) {
TreeMap<Student2, String> tm = new TreeMap<>();
Student2 s1 = new Student2("zoe", 18);
Student2 s2 = new Student2("tony", 62);
Student2 s3 = new Student2("ami", 39);
tm.put(s1, "北京");
tm.put(s2, "上海");
tm.put(s3, "武汉");
System.out.println(tm);
// => {Student2{name='zoe', age=18}=北京, Student2{name='ami', age=39}=武汉, Student2{name='tony', age=62}=上海}
}
}
class Student2 implements Comparable<Student2> {
private String name;
private int age;
public Student2() {
}
@Override
public String toString() {
return "Student2{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student2(String name, int age) {
this.name = name;
this.age = age;
}
@Override
// 自定义排序
public int compareTo(Student2 o) {
int result = this.getAge() - o.getAge();
result = (result == 0 ? this.getName().compareTo(o.getName()) : result);
return result;
}
}
4. 可变参数
可变参数: 就是形参的个数是可以变化的
# 修饰符 返回值类型 方法名(数据类型… 变量名) { }
public static int sum(int… a) { }
特点:
- 可变参数注意事项
- 这里的变量其实是一个数组
- 如果一个方法有多个参数,包含可变参数,可变参数要放在最后
示例:
需求: 实现一个两数相加的方法、实现一个三个数相加的方法, 定义一个方法,求 n 个数相加
public class demo6 {
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
int num3 = 30;
int result1 = getSum(num1, num2);
System.out.println(result1);
// => 30
int result2 = getSum(num1, num2, num3);
System.out.println(result2);
// => 60
int[] arr1 = {1, 2, 3, 4, 5};
int result3 = getSum(arr1);
System.out.println(result3);
// => 15
int result4 = getSum2(1, 2, 4, 5, 6);
System.out.println(result4);
// => 18
}
public static int getSum(int a, int b) {
return a + b;
}
public static int getSum(int a, int b, int c) {
return a + b + c;
}
public static int getSum(int[] arr) {
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
public static int getSum2(int... arr) {
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
}
5. 创建不可变的集合
方法名 | 描述 |
---|---|
static List of(E…elements) | 创建一个具有指定元素的List集合对象 |
static Set of(E…elements) | 创建一个具有指定元素的Set集合对象 |
static <K , V> Map<K,V> of(E…elements) | 创建一个具有指定元素的Map集合对象 |
特点:
- 在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合
- 这个集合不能添加,不能删除,不能修改
- 但是可以结合集合的带参构造,实现集合的批量添加
示例:
public class demo7 {
public static void main(String[] args) {
// static <E> List<E> of(E…elements) 创建一个具有指定元素的List集合对象
List list = List.of("a", "b", "c");
// 报错:java.lang.UnsupportedOperationException 不支持功能异常
// list.add("d");
System.out.println(list);
// => [a, b, c]
// 集合的批量添加
ArrayList<String> arrayList = new ArrayList<>(List.of("a", "bb", "ccc", "dddd"));
System.out.println(arrayList);
// => [a, bb, ccc, dddd]
//static <E> Set<E> of(E…elements) 创建一个具有指定元素的Set集合对象
// 报错:java.lang.IllegalArgumentException 非法论据异常
// Set<String> set = Set.of("a", "b", "c", "a");
Set<String> set = Set.of("a", "b", "c");
System.out.println(set);
// => [a, b, c]
//static <K , V> Map<K,V> of(E…elements)
Map<String, Integer> map = Map.of("zoe", 18, "tony", 65, "ami", 84);
System.out.println(map);
// => {ami=84, zoe=18, tony=65}
// ofEntries
Map<String, Integer> map2 = Map.ofEntries(Map.entry("zoe", 18), Map.entry("tony", 45), Map.entry("ami", 65));
System.out.println(map2);
// => {tony=45, ami=65, zoe=18}
}
}
五、Steam流
1. 对比
需求: 将数组中 z 开头、长度为 3 的名字,循环打印出来
public class demo1 {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>(List.of("zoe", "tony", "ami", "alen", "zero", "zoo"));
System.out.println(list1);
// => [zoe, tony, ami, alen, zero]
// 将 z 开头的名字加入 list2 中
ArrayList<String> list2 = new ArrayList<>();
for (String s : list1) {
if(s.startsWith("z")) {
list2.add(s);
}
}
System.out.println(list2);
// => [zoe, zero]
// 将 list2 中 字符为 3 的名称加入 list3 中, 再循环打印出来
ArrayList<String> list3 = new ArrayList<>();
for (String s : list2) {
if(s.length() == 3) {
list3.add(s);
}
}
// 循环打印
for (String s : list3) {
System.out.println(s);
}
// => zoe, ami
// Stream 方法
list1.stream()
.filter(s -> s.startsWith("z"))
.filter(s -> s.length() == 3)
.forEach(s -> System.out.println(s));
// => zoe, ami
}
}
2. 获取方法
- 单列集合:
- 可以使用Collection接口中的默认方法stream()生成流
- default Stream stream()
- 双列集合:
- 间接的生成流
- 可以先通过keySet或者entrySet获取一个Set集合,再获取Stream流
- 数组:
- Arrays中的静态方法stream 生成流
- 同种数据类型的多个数据:
- 使用Stream.of(T…values)生成流
- 使用Stream.of(T…values)生成流
示例:
public class demo2 {
public static void main(String[] args) {
// 单列集合
ArrayList<String> list = new ArrayList<>(List.of("zoe", "tony", "ami"));
// Stream<String> stream = list.stream();
// stream.forEach(s -> System.out.println(s));
list.stream().forEach(s -> System.out.println(s));
// => zoe,tony,ami
// 双列集合
// HashMap<String, Integer> hm = new HashMap<String, Integer>(List.of("zoe", 18, "tony", 45, "ami", 45));
Map<String, Integer> hm = Map.of("zoe", 18, "tony", 45, "ami", 23);
// keySet() 先获取所有的键,在将集合 Set 放到 Steam 流中
hm.keySet().stream().forEach(s -> System.out.println(s));
// => ami,zoe,tony
// entrySet() 先获取所有的键值对 对象,再将集合 Set中的键值对 对象 放到 Steam 流中
hm.entrySet().stream().forEach(s -> System.out.println(s));
// => tony=45,zoe=18,ami=23
// 数组
int[] arr = {1, 2, 3, 4, 5};
Arrays.stream(arr).forEach(s -> System.out.println(s));
// => 1, 2, 3, 4, 5
// 同种数据类型的多个数据
Stream.of(1, 2, 3, 4, 5, 6).forEach(s -> System.out.println(s));
// => 1, 2, 3, 4, 5, 6
}
}
3. 中间操作方法
方法名 | 描述 |
---|---|
Stream filter(Predicate predicate) | 用于对流中的数据进行过滤 |
Predicate | 接口中的方法 |
boolean test(T t) | 对给定的参数进行判断,返回一个布尔值 |
Stream limit(long maxSize) | 截取指定参数个数的数据 |
Stream skip(long n) | 跳过指定参数个数的数据 |
static Stream concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
Stream distinct() | 去除流中重复的元素。依赖(hashCode和equals方法) |
void forEach(Consumer action) | 对此流的每个元素执行操作 |
Consumer | 接口中的方法 |
void accept(T t) | 对给定的参数执行此操作 |
long count() | 返回此流中的元素数 |
public static Collector toList() | 把元素收集到List集合中 |
public static Collector toSet() | 把元素收集到Set集合中 |
public static Collector toMap(Function keyMapper,Function valueMapper) | 把元素收集到Map集合中 |
示例:
public class demo3 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>(List.of("zoe", "tony", "alen", "zoo", "zoo"));
System.out.println(list);
// => [zoe, tony, ami, alen, zero, zoo, zoo]
// long count():返回此流中的元素数
long count = list.stream().count();
System.out.println(count);
// => 5
// void forEach(Consumer action):对此流的每个元素执行操作
list.stream().forEach(s -> System.out.println(s));
// => zoe,tony, alen, zoo, zoo
// Stream<T> limit(long maxSize):截取指定参数个数的数据
list.stream()
.limit(3)
.forEach(s -> System.out.println(s));
// => zoe,tony, alen
// Stream<T> skip(long n):跳过指定参数个数的数据
list.stream()
.skip(3)
.forEach(s -> System.out.println(s));
// => zoo, zoo
// filter 过滤
list.stream()
.filter(s -> s.length() == 3)
.forEach(s -> System.out.println(s));
// => zoe,zoo, zoo
// Stream<T> distinct():去除流中重复的元素。依赖(hashCode和equals方法)
list.stream()
.distinct()
.forEach(s -> System.out.println(s));
// => zoe,tony, alen, zoo
// static <T> Stream<T> concat(Stream a, Stream b):合并a和b两个流为一个流
ArrayList<String> list2 = new ArrayList<>();
list2.add("other");
Stream.concat(list.stream(), list2.stream()).forEach(s -> System.out.println(s));
// => zoe,tony, alen, zoo, zoo, other
}
}
4. 收集操作方法
方法名 | 描述 |
---|---|
public static Collector toList() | 把元素收集到List集合中 |
public static Collector toSet() | 把元素收集到Set集合中 |
public static Collector toMap(Function keyMapper,Function valueMapper) | 把元素收集到Map集合中 |
需求: 有一个数组 {1, 2, 3, 4, 5}, 去掉其中的奇数,遍历出来
public class demo4 {
public static void main(String[] args) {
// 需求: 有一个数组 {1, 2, 3, 4, 5}, 去掉其中的奇数,遍历出来
// 方法一:
ArrayList<Integer> list = new ArrayList<>(List.of(1, 2, 3, 4, 5));
list.stream()
.filter(s -> s % 2 == 0)
.forEach(s -> System.out.println(s));
// => 2,4
// collect 负责收集数据,获取流中剩余的数据;但是不负责创建容器,也不负责数据添加到容器
List<Integer> list2 = list.stream()
.filter(s -> s % 2 == 0)
.collect(Collectors.toList());
System.out.println(list2);
// => [2, 4]
}
}
六、 File类
1. 概述
File: 它是文件和目录路径名的抽象表示
- 文件和目录可以通过
File
封装成对象 - File封装的对象仅仅是一个路径名;它可以是存在的,也可以是不存在的
2. 构造方法
方法名 | 描述 |
---|---|
File (String pathname) | 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例 |
File (String parent, String child) | 从父路径名字符串和子路径名字符串创建新的 File实例 |
File (File parent, String child) | 从父抽象路径名和子路径名字符串创建新的 File实例 |
示例:
public class demo1 {
public static void main(String[] args) {
// File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的File实例
String path = "src/file/file/txt1.txt";
File file1 = new File(path); // 为了使用File类里面的方法
System.out.println(file1);
// => src/file/file/txt1.txt
// File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的File实例
String path1 = "src/file/file";
String path2 = "txt1.txt";
File file2 = new File(path1, path2);
System.out.println(file2);
// => src/file/file/txt1.txt
// File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的File实例
File file3 = new File("src/file/file");
String path3 = "txt1.txt";
File file4 = new File(file3, path3);
System.out.println(file4);
// => src/file/file/txt1.txt
}
}
3. 创建、删除功能
方法名 | 描述 |
---|---|
public boolean createNewFile() | 创建一个新的空的文件 |
public boolean mkdir() | 创建一个单级文件夹 |
public boolean mkdirs() | 创建一个多级文件夹 |
public boolean delete() | 删除由此抽象路径名表示的文件或目录 |
示例:
public class demo2 {
public static void main(String[] args) throws IOException {
// public boolean createNewFile() 创建一个新的空的文件
File file1 = new File("src/file/file/a.txt");
boolean res1 = file1.createNewFile();
System.out.println(res1);
// => true (如果当前目录存在该文件路径,则创建文件失败)
// public boolean mkdir() 创建一个单级文件夹
File file2 = new File("src/file/file/a");
boolean res2 = file2.mkdir();
System.out.println(res2);
// => true
// public boolean mkdirs() 创建一个多级文件夹
File file3 = new File("src/file/file/bb/cc/dd");
boolean res3 = file3.mkdirs();
System.out.println(res3);
// => true
// delete 删除文件,只能删除文件、空文件夹
File file4 = new File("src/file/file");
boolean res4 = file4.delete();
System.out.println(res4);
// => false
}
}
4. 判断和获取功能
方法名 | 描述 |
---|---|
public boolean isDirectory() | 测试此抽象路径名表示的File是否为目录 |
public boolean isFile() | 测试此抽象路径名表示的File是否为文件 |
public boolean exists() | 测试此抽象路径名表示的File是否存在 |
public String getAbsolutePath() | 返回此抽象路径名的绝对路径名字符串 |
public String getPath() | 将此抽象路径名转换为路径名字符串 |
public String getName() | 返回由此抽象路径名表示的文件或目录的名称 |
示例:
public class demo3 {
public static void main(String[] args) {
//public boolean isDirectory() 测试此抽象路径名表示的File是否为目录
File file1 = new File("src/file/file");
boolean directory = file1.isDirectory();
System.out.println(directory);
// => true
//public boolean isFile() 测试此抽象路径名表示的File是否为文件
File file2 = new File("src/file/file/a.txt");
boolean file = file2.isFile();
System.out.println(file);
// => true
//public boolean exists() 测试此抽象路径名表示的File是否存在
File file3 = new File("src/file/file/b.txt");
boolean exists = file3.exists();
System.out.println(exists);
// => false
//public String getName() 返回由此抽象路径名表示的文件或目录的名称
File file4 = new File("src/file/file/b.txt");
String name = file4.getName();
System.out.println(name);
// => b.txt (当前文件不存在)
}
}
5. 高级获取功能
方法名 | 描述 |
---|---|
public File[] listFiles() | 返回此抽象路径名表示的目录中的文件和目录的File对象数组 |
示例:
public class demo4 {
public static void main(String[] args) {
File file = new File("src/file/file");
File[] files = file.listFiles();
for (File file1 : files) {
System.out.println(file1);
}
// => src/file/file/bb
// => src/file/file/a
// => src/file/file/a.txt
// => src/file/file/txt1.txt
//进入文件夹,获取这个文件夹里面所有的文件和文件夹的File对象,并把这些File对象都放在一个数组中返回.
//包括隐藏文件和隐藏文件夹都可以获取.
// 需求:删除多级文件夹
File src = new File("src/file/bb");
deleteDir(src);
}
public static void deleteDir(File file) {
// 获取文件夹的文件对象
File[] files = file.listFiles();
for (File file1 : files) {
// 如果是文件直接删除
if(file1.isFile()) {
file1.delete();
} else {
// 递归,重复遍历子文件夹,删除子文件夹文件
deleteDir(file1);
}
}
// 最后再删除这个文件夹
file.delete();
}
}
七、 IO
1. 概述
I
表示 intput,是数据从硬盘进内存的过程,称之为读O
表示 output,是数据从内存到硬盘的过程。称之为写
分类:
- 流向分: 输入流、输出流
- 按数据类型分(一般都是按数据类型分类):
- 字节流:操作所有类型的文件
- 字符流:只能操作纯文本文件
2. 字节流
⑴. 写数据流程
public class demo1 {
public static void main(String[] args) throws IOException {
// 创建字节输出流对象
// FileOutputStream fos = new FileOutputStream(new File("src/io/file/a.txt"));
FileOutputStream fos = new FileOutputStream("src/io/file/a.txt");
// 写入数据
fos.write(99);
// 释放空间
fos.close();
}
}
⑵. 写数据的3种方式
方法名 | 描述 |
---|---|
void write (int b) | 一次写一个字节数据 |
void write (byte[] b) | 一次写一个字节数组数据 |
void write (byte[] b, int off, int len) | 一次写一个字节数组的部分数据 |
示例:
public class demo2 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("src/io/file/b.txt");
byte[] bytes = {97, 98, 99};
// fos.write(bytes);
// fos.close();
// => 新增了 b.txt 文件,并添加了内容 abc
// write(写入的数组,从第几个索引,写入个数)
byte[] bytes2 = {97, 98, 99, 100, 101};
fos.write(bytes2, 1, 2);
fos.close();
// => bc (a 对应的是 97 --- 字节数)
}
}
⑶. 写数据拓展功能
1. 实现字节换行
2. 实现数据追加
public class demo3 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("src/io/file/c.txt");
fos.write(97);
// 1. 如何实现字节换行
// windows: \r\n, mac: \r, linux: \n
// getBytes(): 将字符串转换成 对应的 字节码
fos.write("\r".getBytes());
fos.write(98);
fos.close();
// 2. 如何实现数据追加
// 第二个参数为 续写开关,默认为 false
FileOutputStream fos2 = new FileOutputStream("src/io/file/d.txt", true);
fos2.write(97);
fos2.write(98);
fos2.write(99);
fos2.close();
}
}
3. 数字转码(码表)
public class demo4 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("src/io/file/c.txt");
int read = fis.read();
System.out.println(read);
// => 97
// 返回的是字符对应的数字,可以通过 char 强转成 码表对应的 字符
System.out.println((char)read);
fis.close();
// => a
}
}
⑷. 文件的读取和复制
需求: 读取文件多个字节流
public class demo5 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("src/io/file/study.txt");
int b;
while ((b = fis.read()) != -1) {
System.out.println((char)b);
}
fis.close();
// => good good study day day up
}
}
需求: 复制文件
public class demo6 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("src/io/file/study.txt");
FileOutputStream fos = new FileOutputStream("src/io/file/study_copy.txt");
int b;
while ((b = fis.read()) != -1 ) {
fos.write(b);
}
fis.close();
fos.close();
}
}
需求: 复制多个字节
public class demo7 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("src/io/file/study.txt");
FileOutputStream fos = new FileOutputStream("src/io/file/study_copy2.txt");
byte [] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1 ) {
fos.write(bytes, 0, len);
}
fis.close();
fos.close();
}
}
⑸. 字节缓冲流
public class demo8 {
public static void main(String[] args) throws IOException {
// 字节缓冲流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src/io/file/study.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/io/file/study_copy3.txt"));
int len;
while ((len = bis.read()) != -1) {
bos.write(len);
}
bis.close();
bos.close();
}
}
需求: 缓冲流结合数据进行文件拷贝
public class demo9 {
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src/io/file/study.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/io/file/study_copy4.txt"));
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
bis.close();
bos.close();
}
}
3. 字符流
⑴. 编码表
- 计算机中储存的信息都是用二进制数表示的
- 我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果
- 按照某种规则,将字符存储到计算机中,称为编码
- 按照同样的规则,将存储在计算机中的二进制数解析显示出来,称为解码
分类:
- ASCII字符集:
ASCII
(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字,大小写字符和一些常见的标点符号(无中文) - GBK: window系统默认的码表。兼容ASCII码表,也包含了21003个汉字,并支持繁体汉字以及部分日韩文字(中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字)
- Unicode码表: 由国际组织ISO 制定,是统一的万国码,计算机科学领域里的一项业界标准,容纳世界上大多数国家的所有常见文字和符号(: Unicode是万国码,以UTF-8编码后一个中文以三个字节的形式存储)
汉字存储和展示过程解析:
⑵. 编码&解码
类型 | 方法名 | 描述 |
---|---|---|
编码 | byte[] getBytes() | 使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中 |
编码 | byte[] getBytes(String charsetName) | 使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中 |
解码 | String(byte[] bytes) | 通过使用平台的默认字符集解码指定的字节数组来构造新的 String |
解码 | String(byte[] bytes, String charsetName) | 通过指定的字符集解码指定的字节数组来构造新的 String |
⑶. 字符流写数据方法
方法名 | 描述 |
---|---|
void write (int c) | 写一个字符 |
void write (char[] cbuf) | 写入一个字符数组 |
void write (char[] cbuf, int off, int len) | 写入字符数组的一部分 |
void write (String str) | 写一个字符串 |
void write (String str, int off, int len) | 写一个字符串的一部分 |
示例:
public class demo1 {
public static void main(String[] args) throws IOException {
// 创建字符输出流的对象
FileWriter fw = new FileWriter("src/io/file/u.txt");
// void write(int c) 写一个字符
fw.write(97);
// void write(char[] cbuf) 写出一个字符数组
char [] chars = {97, 98, 99};
fw.write(chars);
// void write(char[] cbuf, int off, int len) 写出字符数组的一部分
char [] chars2 = {97, 98, 99, 100};
// 三个参数:数组,开始索引,截取个数
fw.write(chars2, 1, 2);
// void write(String str) 写一个字符串
String poetry = "我本将心向明月";
fw.write(poetry);
// void write(String str, int off, int len) 写一个字符串的一部分
String poetry2 = "奈何明月向沟渠";
fw.write(poetry2, 1, 3);
fw.close();
}
}
⑷. 拓展方法
方法名 | 描述 |
---|---|
flush() | 刷新流,还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
示例:
public class demo2 {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("src/io/file/x.txt");
//flush()刷新流。刷新完毕之后,还可以继续写数据
fw.flush();
fw.write(98);
//close()关闭流。释放资源。一旦关闭,就不能写数据
fw.close();
// IOException 读写数据异常
// fw.write(99);
}
}
⑸. 读数据方法
方法名 | 描述 |
---|---|
int read () | 一次读一个字符数据 |
int read (char[] cbuf) | 一次读一个字符数组数据 |
示例:
public class demo3 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("src/io/file/x.txt");
int ch;
while ((ch = fr.read()) != -1) {
System.out.println((char) ch);
}
// => b
fr.close();
}
}
⑹. 案例
需求: 键盘输入用户名和密码,保存本地储存,用户名和密码独占一行
public class demo4 {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String userName = sc.next();
System.out.println("请输入密码:");
String passWord = sc.next();
FileWriter fw = new FileWriter("src/io/file/y.txt");
fw.write(userName);
fw.write("\n");
fw.write(passWord);
fw.flush();
fw.close();
}
}
⑺. 字符缓冲流
方法名 | 构造方法 | 描述 |
---|---|---|
BufferedWriter | BufferedWriter(Writer out) | 将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途 |
BufferedReader | BufferedReader(Reader in) | 从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途 |
- 字符缓冲输出流
public class demo5 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("src/io/file/y.txt"));
char [] chars = new char[1024];
int len;
while ((len = br.read(chars)) != -1) {
System.out.println(new String(chars, 0, len));
}
}
}
- 字符缓冲输入流
public class demo6 {
public static void main(String[] args) throws IOException {
BufferedWriter fw = new BufferedWriter(new FileWriter("src/io/file/z.txt"));
fw.write(97);
char [] chars = {97, 98, 99};
fw.write(chars);
fw.write(chars, 1, 2);
String poetry = "我本将心向明月";
fw.write(poetry);
fw.write(poetry, 2, 3);
fw.flush();
fw.close();
}
}
⑻. 字符缓冲流特有功能
方法名 | 构造方法 | 描述 |
---|---|---|
BufferedWriter | void newLine() | 写一行行分隔符,行分隔符字符串由系统属性定义 |
BufferedReader | public String readLine() | 读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null |
示例:
public class demo7 {
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("src/io/file/v.txt"));
bw.write("我本将心向明月");
// 跨平台的换行符
bw.newLine();
bw.write("奈何明月照沟渠");
bw.flush();
BufferedReader br = new BufferedReader(new FileReader("src/io/file/v.txt"));
// readline 读一整行数据
String s1 = br.readLine();
System.out.println(s1);
// => 我本将心向明月
String s2 = br.readLine();
System.out.println(s2);
// => 奈何明月照沟渠
String s3 = br.readLine();
System.out.println(s3);
// => null
// 改进
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
bw.close();
}
}
⑼. 案例
需求: 读取文件中的数据,排序后再次写到本地文件
public class demo8 {
public static void main(String[] args) throws IOException {
// 1. 读
BufferedReader br = new BufferedReader(new FileReader("src/io/file/sort.txt"));
String line = br.readLine();
System.out.println(line);
// => 8 2 9 4 7 1 5 6 3
// 1.2 类型转换
String[] split = line.split(" ");
int[] arr = new int[split.length];
for (int i = 0; i < split.length; i++) {
arr[i] = Integer.parseInt(split[i]);
}
// 2. 排序
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
// => [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 3. 写入
BufferedWriter bw = new BufferedWriter(new FileWriter("src/io/file/sort.txt"));
for (int i = 0; i < arr.length; i++) {
bw.write(arr[i] + " ");
}
bw.close();
}
}
八、IO 其他流
1. 转换流
转换流就是来进行字节流和字符流之间转换的
- 输出流: InputStreamReader,从字节流到字符流的桥梁
- 输入流: OutputStreamWriter,从字符流到字节流的桥梁
示例:
package io.io2;
import java.io.*;
import java.nio.charset.Charset;
public class demo9 {
public static void main(String[] args) throws IOException {
// 乱码原因:文件是GBK码表,而idea默认的是UTF-8编码格式
FileReader fr = new FileReader("src/io/file/gbk.txt");
int len;
while ((len = fr.read()) != -1) {
System.out.println((char) len);
}
// 解决乱码问题
InputStreamReader isr = new InputStreamReader(new FileInputStream("src/io/file/gbk.txt"), "GBK");
int ch;
while ((ch = isr.read()) != -1) {
System.out.println((char) ch);
}
// JDK11 后推出的构造方法,用于指定码表
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("src/io/file/gbk.txt"), Charset.forName("GBK"));
int ch2;
while ((ch2 = isr2.read()) != -1) {
System.out.println((char) ch2);
}
}
}
2. 对象操作流
可以把对象以字节的形式写到本地文件,直接打开文件,是读不懂的,需要再次用对象操作流读到内存中。
- 对象操作输入流: ObjectInputStream,就是将对象写到本地文件中,或者在网络中传输对象
- 对象操作输出流(对象反序列化流): ObjectOutputStream,把写到本地文件中的对象读到内存中,或者接收网络中传输的对象
- 写对象 — 序列化
public class demo10 {
public static void main(String[] args) throws IOException {
// 写对象 --- 序列化
User user = new User("yf", 18);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/io/file/w.txt"));
oos.writeObject(user);
oos.close();
}
}
// 类实现序列号,需要实现一个接口 Serializable
class User implements Serializable {
private String userName;
private int age;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
public User() {
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
- 读对象 — 反序列化
public class demo11 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 读对象 --- 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/io/file/w.txt"));
Object o = ois.readObject();
System.out.println(o);
// => User{userName='yf', age=18}
ois.close();
}
}
3. Properties
- 是一个Map体系的集合类
- Properties中有跟IO相关的方法
- 只存字符串
⑴. 集合常用方法
public class demo12 {
public static void main(String[] args) {
Properties prop = new Properties();
// 增
prop.put("zoe", 18);
prop.put("tony", 63);
prop.put("ami", 34);
System.out.println(prop);
// => {tony=63, zoe=18, ami=34}
// 删
prop.remove("zoe");
System.out.println(prop);
// => {tony=63, ami=34}
// 改
prop.put("tony", 81);
System.out.println(prop);
// => {tony=81, ami=34}
// 查
Object ami = prop.get("ami");
System.out.println(ami);
// => 34
// 遍历
Set<Object> objects = prop.keySet();
for (Object object : objects) {
Object o = prop.get(object);
System.out.println(object + "," + o);
}
// tony,81 ami,34
// 遍历 键值对对象
Set<Map.Entry<Object, Object>> entries = prop.entrySet();
for (Map.Entry<Object, Object> entry : entries) {
System.out.println(entry.getKey() + "," + entry.getValue());
}
// tony,81 ami,34
}
}
⑵. Properties作为集合的特有方法
方法名 | 描述 |
---|---|
Object setProperty (String key, String value) | 设置集合的键和值,都是String类型,底层调用 Hashtable方法 put |
String getProperty (String key) | 使用此属性列表中指定的键搜索属性 |
Set stringPropertyNames () | 从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 |
示例:
public class demo13 {
public static void main(String[] args) {
Properties prop = new Properties();
//Object setProperty(String key, String value) --- put
//设置集合的键和值,都是String类型,底层调用 Hashtable方法 put
prop.setProperty("America", "Now York");
prop.setProperty("England", "London");
prop.setProperty("Australia", "Sydney");
System.out.println(prop);
// => {America=Now York, England=London, Australia=Sydney}
//String getProperty(String key) --- get
//使用此属性列表中指定的键搜索属性
String england = prop.getProperty("England");
System.out.println(england);
// => London
//Set<String> stringPropertyNames() --- keySet
//从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串
Set<String> strings = prop.stringPropertyNames();
System.out.println(strings);
// => [America, England, Australia]
// 遍历
for (String string : strings) {
String property = prop.getProperty(string);
System.out.println(string + "," + property);
}
// => America,Now York England,London Australia,Sydney
}
}
⑶. Properties和IO流结合的方法
方法名 | 描述 |
---|---|
void load (InputStream inStream) | 从输入字节流读取属性列表(键和元素对) |
void load (Reader reader) | 从输入字符流读取属性列表(键和元素对) |
void store (OutputStream out, String comments) | 将此属性列表(键和元素对)写入此 Properties表中,以适合于使用 load(InputStream)方法的格式写入输出字节流 |
void store (Writer writer, String comments) | 将此属性列表(键和元素对)写入此 Properties表中,以适合使用 load(Reader)方法的格式写入输出字符流 |
示例:
public class demo14 {
public static void main(String[] args) throws IOException {
Properties prop = new Properties();
FileReader fr = new FileReader("src/io/file/t.txt");
//void load(Reader reader) 将本地文件中的键值对数据读取到集合中
prop.load(fr);
System.out.println(prop);
// => {"America",="Now York","England", "London", "Australia", "Sydney"}
fr.close();
FileWriter fw = new FileWriter("src/io/file/r.txt");
//void store(Writer writer, String comments) 将集合中的数据以键值对形式保存在本地
// 第二个参数为注释,可传 null
prop.store(fw, "README");
fw.close();
}
}
九、多线程
1. 线程相关的概念
- 并行: 在同一时刻,有多个指令在多个CPU上同时执行。
- 并发: 在同一时刻,有多个指令在单个CPU上交替执行。
进程: 是正在运行的软件
- 独立性: 进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
- 动态性: 进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
- 并发性: 任何进程都可以同其他进程一起并发执行
线程: 是进程中的单个顺序控制流,是一条执行路径。
- 单线程: 一个进程如果只有一条执行路径,则称为单线程程序
- 多线程: 一个进程如果有多条执行路径,则称为多线程程序
2. 多线程的实现方式
⑴. Thread类
public class demo1 {
public static void main(String[] args) {
MyThead t1 = new MyThead();
MyThead t2 = new MyThead();
// t1.start();
// t2.start();
// => 线程会 交替 执行
t1.run();
t2.run();
// => 线程会 一个个 执行
}
}
class MyThead extends Thread {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("线程" + i);
}
}
}
⑵. Runnable接口
public class demo2 {
public static void main(String[] args) {
MyRunnable mr1 = new MyRunnable();
Thread t1 = new Thread(mr1);
t1.start();
MyRunnable mr2 = new MyRunnable();
Thread t2 = new Thread(mr2);
t2.start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("多线程第二种方式" + i);
}
}
}
⑶. Callable和Future接口
public class demo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc1 = new MyCallable();
// Thread t1 = new Thread(mc1);
// 可以作为参数传递给 Thread
FutureTask<String> ft = new FutureTask<>(mc1);
// 创建线程对象
Thread t1 = new Thread(ft);
// 开启线程
t1.start();
String s = ft.get();
System.out.println(s);
// => 表白
}
}
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 50; i++) {
System.out.println("表白次数:" + i);
}
// 返回的值代表运行完成的结果
return "答应";
}
}
⑷. 三种方式对比
方法 | 优点 | 缺点 |
---|---|---|
实现Runnable、Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程相对复杂,不能直接使用Thread类中的方法 |
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 可以扩展性较差,不能再继承其他的类 |
3. 线程类的常见方法
方法 | 描述 |
---|---|
String getName() | 返回此线程的名称 |
void setName(String name) | 将此线程的名称更改为等于参数 name |
public static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
public static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒 |
public final void setPriority(int newPriority) | 设置线程的优先级 |
public final int getPriority() | 获取线程的优先级 |
public final void setDaemon(boolean on) | 设置为守护线程 |
实现Runnable、Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 |
4. 示例
⑴. 加载器
public class demo4 {
public static void main(String[] args) throws IOException {
// static ClassLoader getSystemClassLoader() 获取系统类加载器
// InputStream getResourceAsStream(String name) 加载某一个资源文件
// 获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// 利用加载器去加载一个指定文件
// 参数:文件的路径(src 根路径);返回值:字节流
InputStream is = systemClassLoader.getResourceAsStream("prop.properties");
Properties prop = new Properties();
prop.load(is);
System.out.println(prop);
// => {name=zoe, age=18}
is.close();
}
}
⑵. 线程名称
public class demo6 {
public static void main(String[] args) {
thread.MyRunnable2 mr1 = new thread.MyRunnable2();
Thread t1 = new Thread(mr1);
t1.start();
thread.MyRunnable2 mr2 = new thread.MyRunnable2();
Thread t2 = new Thread(mr2);
t2.start();
}
}
class MyRunnable2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
// 获取线程名称
System.out.println(Thread.currentThread().getName() + "多线程第二种方式" + i);
}
}
}
⑶. 线程休眠
public class demo7 {
public static void main(String[] args) throws InterruptedException {
System.out.println("睡觉了");
Thread.sleep(1000);
System.out.println("醒了");
// => 睡觉了 (间隔1000ms) 醒了
}
}
⑷. 优先级
线程有两种调度模型:
- 分时调度模型: 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
- 抢占式调度模型: 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
- Java使用的是抢占式调度模型
public class demo9 {
public static void main(String[] args) {
MyCallable3 mc1 = new MyCallable3();
FutureTask<String> ft1 = new FutureTask<>(mc1);
Thread t1 = new Thread(ft1);
t1.setName("zoe");
t1.start();
System.out.println(t1.getPriority());
// => 5 优先级是 5
t1.setPriority(10);
MyCallable3 mc2 = new MyCallable3();
FutureTask<String> ft2 = new FutureTask<>(mc2);
Thread t2 = new Thread(ft2);
t2.setName("tony");
t2.start();
// 交替执行...
System.out.println(t2.getPriority());
// => 5 优先级是 5
t2.setPriority(1);
// t2 抢占 CPU 线程几率更高,是几率
// 小结:线程优先级范围:1 - 10,默认为 5
}
}
class MyCallable3 implements Callable<String> {
@Override
public String call(){
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
return null;
}
}
⑸. 守护线程
public class demo10 {
public static void main(String[] args) {
MyThread1 th1 = new MyThread1();
MyThread2 th2 = new MyThread2();
th1.setName("女神");
th2.setName("备胎");
// 将 th2 设置为守护线程(当普通线程执行完毕,守护线程也就没有执行的必要了)
th2.setDaemon(true);
th1.start();
th2.start();
// 交替打印...
}
}
class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "---" + i);
}
}
}
class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "---" + i);
}
}
}
⑹. 线程生命周期
5. 线程的安全问题
⑴. 同步代码块
# 锁多条语句操作共享数据,可以使用同步代码块实现
synchronized(任意对象) {
多条语句操作共享数据的代码
}
- 默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭
- 当线程执行完出来了,锁才会自动打开
同步的好处和弊端:
- 好处: 解决了多线程的数据安全问题
- 弊端: 当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
⑵. 同步方法
# 就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数) { }
同步代码块和同步方法的区别:
- 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
- 同步代码块可以指定锁对象,同步方法不能指定锁对象
⑵. Lock锁
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
方法 | 描述 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
ReentrantLock() | 创建一个ReentrantLock的实例 |
⑶. 示例
需求: 100 张票,分三个窗口售卖,请用编程思维实现
public class demo3 {
public static void main(String[] args) {
Ticket3 ticket = new Ticket3();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class Ticket3 implements Runnable {
private int ticket = 100;
private Object obj = new Object();
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// synchronized (obj) {
try {
lock.lock();
if (ticket == 0) {
break;
} else {
Thread.sleep(100);
ticket--;
System.out.println(Thread.currentThread().getName() + "还剩票" + ticket);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
// }
}
}
}
6. 生产者消费者
方法 | 描述 |
---|---|
void wait () | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
void notify () | 唤醒正在等待对象监视器的单个线程 |
void notifyAll () | 唤醒正在等待对象监视器的所有线程 |
示例(生产者消费者模式是一个十分经典的多线程协作的模式):
需求: 模拟消费者和生产者关系,生产者生产 -> 产品+1 -> 生产者等待 -> 消费者消费 产品-1 -> 消费者等待 -> 唤醒生产者
public class demo4 {
public static void main(String[] args) {
Cooker c = new Cooker();
c.start();
Foodie f = new Foodie();
f.start();
}
}
// 消费者(吃货)
class Foodie extends Thread {
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
// 判断汉堡包是否存在
if(Desk.count == 0) {
break;
} else {
if(Desk.flag) {
// 有汉堡包,吃货执行,汉堡包不存在了,汉堡包总数减 1,唤醒生产者
System.out.println("吃货正在吃汉堡包");
Desk.flag = false;
Desk.count--;
Desk.lock.notifyAll();
} else {
// 没有汉堡包,吃货等待执行
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
}
// 生产者(厨师)
class Cooker extends Thread {
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
if(Desk.count == 0) {
break;
} else {
if(Desk.flag) {
// 有汉堡包,厨师等待执行
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
// 无汉堡包,厨师执行,汉堡包改为存在,总数加 1,唤醒消费者
System.out.println("厨师正在做汉堡包");
Desk.flag = true;
// 不增加数量,提前结束进程
// Desk.count++;
Desk.lock.notifyAll();
}
}
}
}
}
}
// 产品(桌子上的汉堡包)
class Desk {
// false 桌上上没有汉堡包,允许厨师执行
public static boolean flag = false;
// 汉堡包的总数
public static int count = 10;
// 锁对象(唯一)
public static final Object lock = new Object();
}