1、Stream 流
1.1、Stream 流的概述
- 在Java 8中,得益于Lambda所带来的函数式编程, 引入了一个全新的Stream流概念。
- 目的:用于简化集合和数组操作的API。
代码演示
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
目标:初步体验Stream流的方便与快捷
*/
public class StreamTest {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
Collections.addAll(names, "张三丰","张无忌","周芷若","赵敏","张强");
System.out.println(names);
//
// // 1、从集合中找出姓张的放到新集合
// List<String> zhangList = new ArrayList<>();
// for (String name : names) {
// if(name.startsWith("张")){
// zhangList.add(name);
// }
// }
// System.out.println(zhangList);
//
// // 2、找名称长度是3的姓名
// List<String> zhangThreeList = new ArrayList<>();
// for (String name : zhangList) {
// if(name.length() == 3){
// zhangThreeList.add(name);
// }
// }
// System.out.println(zhangThreeList);
// 3、使用Stream实现的
names.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
}
}
输出结果
[张三丰, 张无忌, 周芷若, 赵敏, 张强]
张三丰
张无忌
Stream流式思想的核心
- 先得到集合或者数组的Stream流(就是一根传送带)。
- 把元素放上去。
- 然后就用这个Stream流简化的API来方便的操作元素。
1.2、Stream 流的获取
- 获取Stream流
创建一条流水线,并把数据放到流水线上准备进行操作 - 中间方法
流水线上的操作。一次操作完毕之后,还可以继续进行其他操作。 - 终结方法
一个Stream流只能有一个终结方法,是流水线上的最后一个操作
Stream操作集合或者数组的第一步是先得到Stream流,然后才能使用流的功能。
集合获取Stream流的方式
可以使用Collection接口中的默认方法stream()生成流
名称 | 说明 |
---|---|
default Stream stream() | 获取当前集合对象的Stream流 |
数组获取Stream流的方式
名称 | 说明 |
---|---|
public static Stream stream(T[] array) | 获取当前数组的Stream流 |
public static Stream of(T… values) | 获取当前数组/可变数据的Stream流 |
代码演示
import java.util.*;
import java.util.stream.Stream;
/**
目标:Stream流的获取
Stream流式思想的核心:
是先得到集合或者数组的Stream流(就是一根传送带)
然后就用这个Stream流操作集合或者数组的元素。
然后用Stream流简化替代集合操作的API.
集合获取流的API:
(1) default Stream<E> stream();
小结:
集合获取Stream流用: stream();
数组:Arrays.stream(数组) / Stream.of(数组);
*/
public class StreamDemo02 {
public static void main(String[] args) {
/** --------------------Collection集合获取流------------------------------- */
Collection<String> list = new ArrayList<>();
Stream<String> s = list.stream();
/** --------------------Map集合获取流------------------------------- */
Map<String, Integer> maps = new HashMap<>();
// 键流
Stream<String> keyStream = maps.keySet().stream();
// 值流
Stream<Integer> valueStream = maps.values().stream();
// 键值对流(拿整体)
Stream<Map.Entry<String,Integer>> keyAndValueStream = maps.entrySet().stream();
/** ---------------------数组获取流------------------------------ */
String[] names = {"赵敏","小昭","灭绝","周芷若"};
Stream<String> nameStream = Arrays.stream(names);
Stream<String> nameStream2 = Stream.of(names);
}
}
1.3、Stream 流的常用API
Stream流的常用API(中间操作方法)
名称 | 说明 |
---|---|
Stream filter(Predicate<? super T> predicate) | 用于对流中的数据进行过滤。 |
Stream limit(long maxSize) | 获取前几个元素 |
Stream skip(long n) | 跳过前几个元素 |
Stream distinct() | 去除流中重复的元素。依赖(hashCode和equals方法) |
static Stream concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
注意:
- 中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。
- 在Stream流中无法直接修改集合、数组中的数据。
Stream流的常见终结操作方法
名称 | 说明 |
---|---|
void forEach(Consumer action) | 对此流的每个元素执行遍历操作 |
long count() | 返回此流中的元素数 |
注意:终结操作方法,调用完成后流就无法继续使用了,原因是不会返回Stream了。
代码演示
学生类
public class Student {
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
测试类
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/**
目标:Stream流的常用API
forEach : 逐一处理(遍历)
count:统计个数
-- long count();
filter : 过滤元素
-- Stream<T> filter(Predicate<? super T> predicate)
limit : 取前几个元素
skip : 跳过前几个
map : 加工方法
concat : 合并流。
*/
public class StreamDemo03 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
list.add("张三丰");
// Stream<T> filter(Predicate<? super T> predicate)
list.stream().filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));
long size = list.stream().filter(s -> s.length() == 3).count();
System.out.println(size);
// list.stream().filter(s -> s.startsWith("张")).limit(2).forEach(s -> System.out.println(s));
list.stream().filter(s -> s.startsWith("张")).limit(2).forEach(System.out::println);
list.stream().filter(s -> s.startsWith("张")).skip(2).forEach(System.out::println);
// map加工方法: 第一个参数原材料 -> 第二个参数是加工后的结果。
// 给集合元素的前面都加上一个:黑马的:
list.stream().map(s -> "黑马的:" + s).forEach(a -> System.out.println(a));
// 需求:把所有的名称 都加工成一个学生对象。
list.stream().map(s -> new Student(s)).forEach(s -> System.out.println(s));
// list.stream().map(Student::new).forEach(System.out::println); // 构造器引用 方法引用
// 合并流。
Stream<String> s1 = list.stream().filter(s -> s.startsWith("张"));
Stream<String> s2 = Stream.of("java1", "java2");
// public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
Stream<String> s3 = Stream.concat(s1 , s2);
s3.distinct().forEach(s -> System.out.println(s));
}
输出结果
张无忌
张强
张三丰
张三丰
4
张无忌
张强
张三丰
张三丰
黑马的:张无忌
黑马的:周芷若
黑马的:赵敏
黑马的:张强
黑马的:张三丰
黑马的:张三丰
Student{name='张无忌'}
Student{name='周芷若'}
Student{name='赵敏'}
Student{name='张强'}
Student{name='张三丰'}
Student{name='张三丰'}
张无忌
张强
张三丰
java1
java2
1.4、Stream 流的综合应用
需求:
- 某个公司的开发部门,分为开发一部和二部,现在需要进行年中数据结算。
分析:
- 员工信息至少包含了(名称、性别、工资、奖金、处罚记录)
- 开发一部有4个员工、开发二部有5名员工
- 分别筛选出2个部门的最高工资的员工信息,封装成优秀员工对象Topperformer
- 分别统计出2个部门的平均月收入,要求去掉最高和最低工资。
- 统计2个开发部门整体的平均工资,去掉最低和最高工资的平均值。
代码演示
员工类
public class Employee {
private String name;
private char sex;
private double salary;
private double bonus;
private String punish; // 处罚信息
public Employee(){
}
public Employee(String name, char sex, double salary, double bonus, String punish) {
this.name = name;
this.sex = sex;
this.salary = salary;
this.bonus = bonus;
this.punish = punish;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
public String getPunish() {
return punish;
}
public void setPunish(String punish) {
this.punish = punish;
}
public double getTotalSalay(){
return salary * 12 + bonus;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", sex=" + sex +
", salary=" + salary +
", bonus=" + bonus +
", punish='" + punish + '\'' +
'}'+"\n";
}
}
Topperformer 类
public class Topperformer {
private String name;
private double money; // 月薪
public Topperformer() {
}
public Topperformer(String name, double money) {
this.name = name;
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Topperformer{" +
"name='" + name + '\'' +
", money=" + money +
'}';
}
}
实现类
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamDemo04 {
public static double allMoney ;
public static double allMoney2 ; // 2个部门去掉最高工资,最低工资的总和
public static void main(String[] args) {
List<Employee> one = new ArrayList<>();
one.add(new Employee("猪八戒",'男',30000 , 25000, null));
one.add(new Employee("孙悟空",'男',25000 , 1000, "顶撞上司"));
one.add(new Employee("沙僧",'男',20000 , 20000, null));
one.add(new Employee("小白龙",'男',20000 , 25000, null));
List<Employee> two = new ArrayList<>();
two.add(new Employee("武松",'男',15000 , 9000, null));
two.add(new Employee("李逵",'男',20000 , 10000, null));
two.add(new Employee("西门庆",'男',50000 , 100000, "被打"));
two.add(new Employee("潘金莲",'女',3500 , 1000, "被打"));
two.add(new Employee("武大郎",'女',20000 , 0, "下毒"));
// 1、开发一部的最高工资的员工。(API)
// 指定大小规则了
// Employee e = one.stream().max((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
// .get();
// System.out.println(e);
Topperformer t = one.stream().max((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
.map(e -> new Topperformer(e.getName(), e.getSalary() + e.getBonus())).get();
System.out.println(t);
// 2、统计平均工资,去掉最高工资和最低工资
one.stream().sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
.skip(1).limit(one.size() - 2).forEach(e -> {
// 求出总和:剩余员工的工资总和
allMoney += (e.getSalary() + e.getBonus());
});
System.out.println("开发一部的平均工资是:" + allMoney / (one.size() - 2));
// 3、合并2个集合流,再统计
Stream<Employee> s1 = one.stream();
Stream<Employee> s2 = two.stream();
Stream<Employee> s3 = Stream.concat(s1 , s2);
s3.sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
.skip(1).limit(one.size() + two.size() - 2).forEach(e -> {
// 求出总和:剩余员工的工资总和
allMoney2 += (e.getSalary() + e.getBonus());
});
// BigDecimal
BigDecimal a = BigDecimal.valueOf(allMoney2);
BigDecimal b = BigDecimal.valueOf(one.size() + two.size() - 2);
System.out.println("开发部的平均工资是:" + a.divide(b,2, RoundingMode.HALF_UP));
}
}
输出结果
Topperformer{name='猪八戒', money=55000.0}
开发一部的平均工资是:42500.0
开发部的平均工资是:34285.71
1.5、收集 Stream 流
Stream流的收集操作
- 收集Stream流的含义:就是把Stream流操作后的结果数据转回到集合或者数组中去。
- Stream流:方便操作集合/数组的手段。
- 集合/数组:才是开发中的目的。
Stream流的收集方法
名称 | 说明 |
---|---|
R collect(Collector collector) | 开始收集Stream流,指定收集器 |
Collectors工具类提供了具体的收集方式
名称 | 说明 |
---|---|
public static Collector toList() | 把元素收集到List集合中 |
public static Collector toSet() | 把元素收集到Set集合中 |
public static Collector toMap(Function keyMapper , Function valueMapper) | 把元素收集到Map集合中 |
代码演示
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
目标:收集Stream流的数据到 集合或者数组中去。
*/
public class StreamDemo05 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
list.add("张三丰");
Stream<String> s1 = list.stream().filter(s -> s.startsWith("张"));
List<String> zhangList = s1.collect(Collectors.toList()); // 可变集合
zhangList.add("java1");
System.out.println(zhangList);
// List<String> list1 = s1.toList(); // 得到不可变集合
// list1.add("java");
// System.out.println(list1);
// 注意注意注意:“流只能使用一次”
Stream<String> s2 = list.stream().filter(s -> s.startsWith("张"));
Set<String> zhangSet = s2.collect(Collectors.toSet());
System.out.println(zhangSet);
Stream<String> s3 = list.stream().filter(s -> s.startsWith("张"));
// Object[] arrs = s3.toArray();
String[] arrs = s3.toArray(String[]::new); // 可以不管,拓展一下思维!!
System.out.println("Arrays数组内容:" + Arrays.toString(arrs));
}
}
输出结果
[张无忌, 张强, 张三丰, 张三丰, java1]
[张强, 张三丰, 张无忌]
Arrays数组内容:[张无忌, 张强, 张三丰, 张三丰]
补充
jdk16 开始可以直接调用 toList() 方法得到一个不可变集合
2、异常处理
2.1、异常概述、体系
什么是异常?
- 异常是程序在“编译”或者“执行”的过程中可能出现的问题,注意:语法错误不算在异常体系中。
- 比如:数组索引越界、空指针异常、 日期格式化异常,等…
为什么要学习异常?
- 异常一旦出现了,如果没有提前处理,程序就会退出JVM虚拟机而终止。
- 研究异常并且避免异常,然后提前处理异常,体现的是程序的安全, 健壮性。
异常体系
编译时异常和运行时异常
2.2、常见运行时异常
运行时异常
直接继承自RuntimeException或者其子类,编译阶段不会报错,运行时可能出现的错误。
运行时异常示例
- 数组索引越界异常: ArrayIndexOutOfBoundsException。
- 空指针异常 : NullPointerException。直接输出没有问题。但是调用空指针的变量的功能就会报错!!
- 类型转换异常:ClassCastException。
- 迭代器遍历没有此元素异常:NoSuchElementException。
- 数学操作异常:ArithmeticException。
- 数字转换异常: NumberFormatException。
运行时异常:一般是程序员业务没有考虑好或者是编程逻辑不严谨引起的程序错误,自己的水平有问题!
代码演示
/**
拓展: 常见的运行时异常。(面试题)
运行时异常的概念:
继承自RuntimeException的异常或者其子类,
编译阶段是不会出错的,它是在运行时阶段可能出现的错误,
运行时异常编译阶段可以处理也可以不处理,代码编译都能通过!!
1.数组索引越界异常: ArrayIndexOutOfBoundsException。
2.空指针异常 : NullPointerException。
直接输出没有问题。但是调用空指针的变量的功能就会报错!!
3.类型转换异常:ClassCastException。
4.迭代器遍历没有此元素异常:NoSuchElementException。
5.数学操作异常:ArithmeticException。
6.数字转换异常: NumberFormatException。
小结:
运行时异常继承了RuntimeException ,编译阶段不报错,运行时才可能会出现错误!
*/
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("程序开始。。。。。。");
/** 1.数组索引越界异常: ArrayIndexOutOfBoundsException。*/
int[] arr = {1, 2, 3};
System.out.println(arr[2]);
// System.out.println(arr[3]); // 运行出错,程序终止
/** 2.空指针异常 : NullPointerException。直接输出没有问题。但是调用空指针的变量的功能就会报错!! */
String name = null;
System.out.println(name); // null
// System.out.println(name.length()); // 运行出错,程序终止
/** 3.类型转换异常:ClassCastException。 */
Object o = 23;
// String s = (String) o; // 运行出错,程序终止
/** 5.数学操作异常:ArithmeticException。 */
//int c = 10 / 0;
/** 6.数字转换异常: NumberFormatException。 */
//String number = "23";
String number = "23aabbc";
Integer it = Integer.valueOf(number); // 运行出错,程序终止
System.out.println(it + 1);
System.out.println("程序结束。。。。。");
}
}
2.3、常见编译时异常
编译时异常
不是RuntimeException或者其子类的异常,编译阶就报错,必须处理,否则代码不通过。
编译时异常的作用
- 是担心程序员的技术不行,在编译阶段就爆出一个错误, 目的在于提醒不要出错!
- 编译时异常是可遇不可求。遇到了就遇到了呗。
代码演示
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
目标:常见的编译时异常认识。
编译时异常:继承自Exception的异常或者其子类,没有继承RuntimeException
"编译时异常是编译阶段就会报错",
必须程序员编译阶段就处理的。否则代码编译就报错!!
编译时异常的作用是什么:
是担心程序员的技术不行,在编译阶段就爆出一个错误, 目的在于提醒!
提醒程序员这里很可能出错,请检查并注意不要出bug。
编译时异常是可遇不可求。遇到了就遇到了呗。
了解:
*/
public class ExceptionDemo {
public static void main(String[] args) throws ParseException {
String date = "2015-01-12 10:23:21";
// 创建一个简单日期格式化类:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
// 解析字符串时间成为日期对象
Date d = sdf.parse(date);
//
System.out.println(d);
}
}
输出结果
Exception in thread "main" java.text.ParseException: Unparseable date: "2015-01-12 10:23:21"
2.4、异常的默认处理流程
- 默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException。
- 异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。
- 虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
- 直接从当前执行的异常点干掉当前程序。
- 后续代码没有机会执行了,因为程序已经死亡。
代码演示
/**
目标:异常的产生默认的处理过程解析。(自动处理的过程!)
(1)默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException。
(2)异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。
(3)虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
(4)直接从当前执行的异常点干掉当前程序。
(5)后续代码没有机会执行了,因为程序已经死亡。
小结:
异常一旦出现,会自动创建异常对象,最终抛出给虚拟机,虚拟机
只要收到异常,就直接输出异常信息,干掉程序!!
默认的异常处理机制并不好,一旦真的出现异常,程序立即死亡!
*/
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("程序开始。。。。。。。。。。");
chu(10, 0);
System.out.println("程序结束。。。。。。。。。。");
}
public static void chu(int a , int b){
System.out.println(a);
System.out.println(b);
int c = a / b;
System.out.println(c);
}
}
输出结果
程序开始。。。。。。。。。。
10
0
Exception in thread "main" java.lang.ArithmeticException: / by zero
2.5、编译时异常的处理机制
- 编译时异常是编译阶段就出错的,所以必须处理,否则代码根本无法通过。
- 编译时异常的处理形式有三种:
- 出现异常直接抛出去给调用者,调用者也继续抛出去。
- 出现异常自己捕获处理,不麻烦别人。
- 前两者结合,出现异常直接抛出去给调用者,调用者捕获处理。
2.5.1、异常处理方式1 —— throws
代码演示
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
目标:编译时异常的处理方式一。
编译时异常:编译阶段就会报错,一定需要程序员处理的,否则代码无法通过!!
抛出异常格式:
方法 throws 异常1 , 异常2 , ..{
}
建议抛出异常的方式:代表可以抛出一切异常,
方法 throws Exception{
}
方式一:
在出现编译时异常的地方层层把异常抛出去给调用者,调用者最终抛出给JVM虚拟机。
JVM虚拟机输出异常信息,直接干掉程序,这种方式与默认方式是一样的。
虽然可以解决代码编译时的错误,但是一旦运行时真的出现异常,程序还是会立即死亡!
这种方式并不好!
小结:
方式一出现异常层层跑出给虚拟机,最终程序如果真的出现异常,程序还是立即死亡!这种方式不好!
*/
public class ExceptionDemo01 {
// public static void main(String[] args) throws ParseException, FileNotFoundException {
// System.out.println("程序开始。。。。。");
// parseTime("2011-11-11 11:11:11");
// System.out.println("程序结束。。。。。");
// }
//
// public static void parseTime(String date) throws ParseException, FileNotFoundException {
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
// Date d = sdf.parse(date);
// System.out.println(d);
//
// InputStream is = new FileInputStream("E:/meinv.jpg");
// }
public static void main(String[] args) throws Exception {
System.out.println("程序开始。。。。。");
parseTime("2011-11-11 11:11:11");
System.out.println("程序结束。。。。。");
}
public static void parseTime(String date) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(date);
System.out.println(d);
InputStream is = new FileInputStream("E:/meinv.jpg");
}
}
输出结果
程序开始。。。。。
Fri Nov 11 11:11:11 CST 2011
Exception in thread "main" java.io.FileNotFoundException: E:\meinv.jpg (系统找不到指定的路径。)
2.5.2、异常处理方式2 —— try…catch…
代码演示
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
目标:编译时异常的处理方式二。
方式二:在出现异常的地方自己处理,谁出现谁处理。
自己捕获异常和处理异常的格式:捕获处理
try{
// 监视可能出现异常的代码!
}catch(异常类型1 变量){
// 处理异常
}catch(异常类型2 变量){
// 处理异常
}...
监视捕获处理异常企业级写法:
try{
// 可能出现异常的代码!
}catch (Exception e){
e.printStackTrace(); // 直接打印异常栈信息
}
Exception可以捕获处理一切异常类型!
小结:
第二种方式,可以处理异常,并且出现异常后代码也不会死亡。
这种方案还是可以的。
但是从理论上来说,这种方式不是最好的,上层调用者不能直接知道底层的执行情况!
*/
public class ExceptionDemo02 {
public static void main(String[] args) {
System.out.println("程序开始。。。。");
parseTime("2011-11-11 11:11:11");
System.out.println("程序结束。。。。");
}
public static void parseTime(String date) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
Date d = sdf.parse(date);
System.out.println(d);
InputStream is = new FileInputStream("E:/meinv.jpg");
} catch (Exception e) {
e.printStackTrace(); // 打印异常栈信息
}
}
// public static void parseTime(String date) {
// try {
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
// Date d = sdf.parse(date);
// System.out.println(d);
//
// InputStream is = new FileInputStream("E:/meinv.jpg");
// } catch (FileNotFoundException|ParseException e) {
// e.printStackTrace(); // 打印异常栈信息
// }
// }
// public static void parseTime(String date) {
// try {
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
// Date d = sdf.parse(date);
// System.out.println(d);
//
// InputStream is = new FileInputStream("E:/meinv.jpg");
// } catch (FileNotFoundException e) {
// e.printStackTrace(); // 打印异常栈信息
// } catch (ParseException e) {
// e.printStackTrace();
// }
// }
// public static void parseTime(String date) {
// try {
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
// Date d = sdf.parse(date);
// System.out.println(d);
// } catch (ParseException e) {
// // 解析出现问题
// System.out.println("出现了解析时间异常哦,走点心!!");
// }
//
// try {
// InputStream is = new FileInputStream("E:/meinv.jpg");
// } catch (FileNotFoundException e) {
// System.out.println("您的文件根本就没有啊,不要骗我哦!!");
// }
// }
}
输出结果
程序开始。。。。
java.text.ParseException: Unparseable date: "2011-11-11 11:11:11"
程序结束。。。。
2.5.3、异常处理方式3 —— 前两者结合
- 方法直接将异通过throws抛出去给调用者
- 调用者收到异常后直接捕获处理。
代码演示
import java.io.FileInputStream;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
目标:编译时异常的处理方式三。
方式三: 在出现异常的地方把异常一层一层的抛出给最外层调用者,
最外层调用者集中捕获处理!!(规范做法)
小结:
编译时异常的处理方式三:底层出现的异常抛出给最外层调用者集中捕获处理。
这种方案最外层调用者可以知道底层执行的情况,同时程序在出现异常后也不会立即死亡,这是
理论上最好的方案。
虽然异常有三种处理方式,但是开发中只要能解决你的问题,每种方式都又可能用到!!
*/
public class ExceptionDemo03 {
public static void main(String[] args) {
System.out.println("程序开始。。。。");
try {
parseTime("2011-11-11 11:11:11");
System.out.println("功能操作成功~~~");
} catch (Exception e) {
e.printStackTrace();
System.out.println("功能操作失败~~~");
}
System.out.println("程序结束。。。。");
}
public static void parseTime(String date) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy、MM-dd HH:mm:ss");
Date d = sdf.parse(date);
System.out.println(d);
InputStream is = new FileInputStream("D:/meinv.jpg");
}
}
输出结果
程序开始。。。。
java.text.ParseException: Unparseable date: "2011-11-11 11:11:11"
功能操作失败~~~
程序结束。。。。
2.5.4、异常处理的总结
- 在开发中按照规范来说第三种方式是最好的:底层的异常抛出去给最外层,最外层集中捕获处理。
- 实际应用中,只要代码能够编译通过,并且功能能完成,那么每一种异常处理方式似乎也都是可以的。
2.6、运行时异常的处理机制
- 运行时异常编译阶段不会出错,是运行时才可能出错的,所以编译阶段不处理也可以。
- 按照规范建议还是处理:建议在最外层调用处集中捕获处理即可。
代码演示
/**
目标:运行时异常的处理机制。
可以不处理,编译阶段又不报错。
按照理论规则:建议还是处理,只需要在最外层捕获处理即可
*/
public class Test {
public static void main(String[] args) {
System.out.println("程序开始。。。。。。。。。。");
try {
chu(10, 0);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("程序结束。。。。。。。。。。");
}
public static void chu(int a , int b) { // throws RuntimeException{
System.out.println(a);
System.out.println(b);
int c = a / b;
System.out.println(c);
}
}
输出结果
程序开始。。。。。。。。。。
10
0
程序结束。。。。。。。。。。
java.lang.ArithmeticException: / by zero
2.7、异常处理使代码更稳健的案例
**需求:**键盘录入一个合理的价格为止(必须是数值,值必须大于0)。
**分析:**定义一个死循环,让用户不断的输入价格。
代码演示
import java.util.Scanner;
/**
需求:需要输入一个合法的价格为止 要求价格大于 0
*/
public class Test2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (true) {
try {
System.out.println("请您输入合法的价格:");
String priceStr = sc.nextLine();
// 转换成double类型的价格
double price = Double.valueOf(priceStr);
// 判断价格是否大于 0
if(price > 0) {
System.out.println("定价:" + price);
break;
}else {
System.out.println("价格必须是正数~~~");
}
} catch (Exception e) {
System.out.println("用户输入的数据有毛病,请您输入合法的数值,建议为正数~~");
}
}
}
}
2.8、自定义异常
自定义异常的必要
- Java无法为这个世界上全部的问题提供异常类。
- 如果企业想通过异常的方式来管理自己的某个业务问题,就需要自定义异常类了。
自定义异常的好处
- 可以使用异常的机制管理业务问题,如提醒程序员注意。
- 同时一旦出现bug,可以用异常的形式清晰的指出出错的地方。
自定义异常的分类
1、自定义编译时异常
- 定义一个异常类继承Exception.
- 重写构造器。
- 在出现异常的地方用throw new 自定义对象抛出。
- 作用:编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!
2、自定义运行时异常
- 定义一个异常类继承RuntimeException.
- 重写构造器。
- 在出现异常的地方用throw new 自定义对象抛出!
- 作用:提醒不强烈,编译阶段不报错!!运行时才可能出现!!
代码演示
自定义编译时异常
/**
自定义的编译时异常
1、继承Exception
2、重写构造器
*/
public class ItheimaAgeIlleagalException extends Exception{
public ItheimaAgeIlleagalException() {
}
public ItheimaAgeIlleagalException(String message) {
super(message);
}
}
自定义运行时异常
/**
自定义的运行时异常
1、继承RuntimeException
2、重写构造器
*/
public class ItheimaAgeIlleagalRuntimeException extends RuntimeException{
public ItheimaAgeIlleagalRuntimeException() {
}
public ItheimaAgeIlleagalRuntimeException(String message) {
super(message);
}
}
测试类
/**
目标:自定义异常(了解)
引入:Java已经为开发中可能出现的异常都设计了一个类来代表.
但是实际开发中,异常可能有无数种情况,Java无法为
这个世界上所有的异常都定义一个代表类。
假如一个企业如果想为自己认为的某种业务问题定义成一个异常
就需要自己来自定义异常类.
需求:认为年龄小于0岁,大于200岁就是一个异常。
自定义异常:
自定义编译时异常.
a.定义一个异常类继承Exception.
b.重写构造器。
c.在出现异常的地方用throw new 自定义对象抛出!
编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!
自定义运行时异常.
a.定义一个异常类继承RuntimeException.
b.重写构造器。
c.在出现异常的地方用throw new 自定义对象抛出!
提醒不强烈,编译阶段不报错!!运行时才可能出现!!
*/
public class ExceptionDemo {
public static void main(String[] args) {
// try {
// checkAge(-34);
// } catch (ItheimaAgeIlleagalException e) {
// e.printStackTrace();
// }
try {
checkAge2(-23);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void checkAge2(int age) {
if(age < 0 || age > 200){
// 抛出去一个异常对象给调用者
// throw :在方法内部直接创建一个异常对象,并从此点抛出
// throws : 用在方法申明上的,抛出方法内部的异常
throw new ItheimaAgeIlleagalRuntimeException(age + " is illeagal!");
}else {
System.out.println("年龄合法:推荐商品给其购买~~");
}
}
public static void checkAge(int age) throws ItheimaAgeIlleagalException {
if(age < 0 || age > 200){
// 抛出去一个异常对象给调用者
// throw :在方法内部直接创建一个异常对象,并从此点抛出
// throws : 用在方法申明上的,抛出方法内部的异常
throw new ItheimaAgeIlleagalException(age + " is illeagal!");
}else {
System.out.println("年龄合法:推荐商品给其购买~~");
}
}
}
输出结果
com.itheima.d9_exception_custom.ItheimaAgeIlleagalRuntimeException: -23 is illeagal!
注意
- throw :在方法内部直接创建一个异常对象,并从此点抛出
- throws : 用在方法申明上的,抛出方法内部的异常
3、日志框架
3.1、日志技术的概述
想清楚的知道一个系统运行的过程和详情怎么办?
日志
- 生活中的日志: 生活中的日志就好比日记,可以记录你生活的点点滴滴。
- 程序中的日志: 程序中的日志可以用来记录程序运行过程中的信息,并可以进行永久存储。
输出语句记录日志的弊端
- 信息只能展示在控制台
- 不能将其记录到其他的位置(文件,数据库)
- 想取消记录的信息需要修改代码才可以完成
日志技术具备的优势
- 可以将系统执行的信息选择性的记录到指定的位置(控制台、文件中、数据库中)。
- 可以随时以开关的形式控制是否记录日志,无需修改源代码。
输出语句 | 日志技术 | |
---|---|---|
输出位置 | 只能是控制台 | 可以将日志信息写入到文件或者数据库中 |
取消日志 | 需要修改代码,灵活性比较差 | 不需要修改代码,灵活性比较好 |
多线程 | 性能较差 | 性能较好 |
3.2、日志技术体系结构
3.3、Logback概述
Logback日志框架
- Logback是由log4j创始人设计的另一个开源日志组件,性能比log4j要好
- 官方网站:https://logback.qos.ch/index.html
- Logback是基于slf4j的日志规范实现的框架。
Logback主要分为三个技术模块
- logback-core: logback-core 模块为其他两个模块奠定了基础,必须有。
- logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API。
- logback-access 模块与 Tomcat 和 Jetty 等 Servlet 容器集成,以提供 HTTP 访问日志功能
3.4、Logback快速入门
导入Logback日志技术到项目中,用于纪录系统的日志信息
-
在项目下新建文件夹lib,导入Logback的相关jar包到该文件夹下,并添加到项目依赖库中去。
-
将Logback的核心配置文件logback.xml直接拷贝到src目录下(必须是src下)。
-
在代码中获取日志的对象
public static final Logger LOGGER = LoggerFactory.getLogger("Test.class");
-
使用日志对象LOGGER调用其方法输出日志信息
LOGGER.error("无法解析该时间格式");
3.5、Logback配置详解-输出位置、格式设置
Logback日志系统的特性都是通过核心配置文件logback.xml控制的。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--输出流对象 默认 System.out 改为 System.err-->
<target>System.out</target>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n</pattern>
</encoder>
</appender>
<!-- File是输出的方向通向文件的 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--日志输出路径-->
<file>D:/日志记录/data.log</file>
<!--指定日志文件拆分和压缩规则-->
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通过指定压缩文件名称,来确定分割文件方式-->
<fileNamePattern>D:/日志记录/data-%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
<!--文件拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>
<!--
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR | ALL 和 OFF
, 默认debug
<root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
-->
<root level="ALL">
<!-- 注意:如果这里不配置关联打印位置,该位置将不会记录日志-->
<appender-ref ref="FILE" />
</root>
</configuration>
Logback日志输出位置、格式设置
- 通过logback.xml 中的标签可以设置输出位置和日志信息的详细格式。
- 通常可以设置2个日志输出位置:一个是控制台、一个是系统文件中
输出到控制台的配置标志
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
输出到系统文件的配置标志
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
3.6、Logback配置详解-日志级别设置
如果系统上线后只想记录一些错误的日志信息或者不想记录日志了,怎么办?
可以通过设置日志的输出级别来控制哪些日志信息输出或者不输出。
日志级别
- 级别程度依次是:TRACE< DEBUG< INFO<WARN<ERROR ; 默认级别是debug(忽略大小写),对应其方法。
- 作用:用于控制系统中哪些日志级别是可以输出的,只输出级别不低于设定级别的日志信息。
- ALL 和 OFF分别是打开全部日志信息,及关闭全部日志信息。
- 具体在标签的level属性中设置日志级别。
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
4、阶段项目实战
完场电影购票系统
4.1、日志框架搭建
集成日志框架、用于后期记录日志信息。
xml 文件配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--输出流对象 默认 System.out 改为 System.err-->
<target>System.out</target>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n</pattern>
</encoder>
</appender>
<!-- File是输出的方向通向文件的 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--日志输出路径-->
<file>D:/Product/data.log</file>
<!--指定日志文件拆分和压缩规则-->
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通过指定压缩文件名称,来确定分割文件方式-->
<fileNamePattern>D:/Product/data-%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
<!--文件拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>
<!--
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR | ALL 和 OFF
, 默认debug
<root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
-->
<root level="ALL">
<!-- 注意:如果这里不配置关联打印位置,该位置将不会记录日志-->
<appender-ref ref="FILE" />
</root>
</configuration>
创建日志对象
public static final Logger LOGGER = LoggerFactory.getLogger("MovieSystem.class");
4.2、系统角色分析
- 定义一个电影类Movie类,Movie类包含:片名、主演、评分、时长、票价、余票
- 系统包含2个用户角色:客户、商家。存在大量相同属性信息。
- 定义User类作为父类,属性:登录名称、密码、真实名称、性别、电话、账户金额
- 定义Business类代表商家角色,属性:店铺名称、地址。
- 定义Customer类代表客户角色,属性:
- 定义集合List用户存放系统注册的用户对象信息。
- 定义集合Map<Business, List>存放商家和其排片信息。
- 准备一些测试数据。
定义Movie类
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
public class Movie {
private String name;
private String actor;
private double time;
private double price;
private int number; // 余票
private Date startTime; // 放映时间
public Movie() {
}
public Movie(String name, String actor, double time, double price, int number, Date startTime) {
this.name = name;
this.actor = actor;
this.time = time;
this.price = price;
this.number = number;
this.startTime = startTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getActor() {
return actor;
}
public void setActor(String actor) {
this.actor = actor;
}
public double getScore() {
List<Double> scores = MovieSystem.MOVIES_SCORE.get(name);
if(scores!=null && scores.size() > 0){
double sum = 0;
for (Double score : scores) {
sum += score;
}
return BigDecimal.valueOf(sum).divide(BigDecimal.valueOf(scores.size()), 2 , RoundingMode.UP).doubleValue();
}else {
return 0;
}
}
public double getTime() {
return time;
}
public void setTime(double time) {
this.time = time;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Date getStartTime() {
return startTime;
}
public void setStartTime(Date startTime) {
this.startTime = startTime;
}
}
定义User类
/**
用户类(客户和商家的父类 )
*/
public class User {
private String loginName; // 假名 不能重复
private String userName; // 真名
private String passWord;
private char sex;
private String phone;
private double money;
public User(){
}
public User(String loginName, String userName, String passWord, char sex, String phone, double money) {
this.loginName = loginName;
this.userName = userName;
this.passWord = passWord;
this.sex = sex;
this.phone = phone;
this.money = money;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
定义Business类
public class Business extends User{
// 店铺名称
private String shopName;
// 店铺地址
private String address;
public String getShopName() {
return shopName;
}
public void setShopName(String shopName) {
this.shopName = shopName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
定义Customer类
import java.util.HashMap;
import java.util.Map;
/**
客户角色
*/
public class Customer extends User{
// 定义一个属性存储购买记录。
private Map<String, Boolean> buyMovies = new HashMap<>();
public Map<String, Boolean> getBuyMovies() {
return buyMovies;
}
public void setBuyMovies(Map<String, Boolean> buyMovies) {
this.buyMovies = buyMovies;
}
}
定义集合List用户存放系统注册的用户对象信息
/**
定义系统的数据容器用户存储数据
1、存储很多用户(客户对象,商家对象)
*/
public static final List<User> ALL_USERS = new ArrayList<>();
定义集合Map<Business, List>存放商家和其排片信息
/**
2、存储系统全部商家和其排片信息 。
商家1 = [p1,p2,p3,...]
商家2 = [p2,p3,...]
...
*/
public static final Map<Business, List<Movie>> ALL_MOVIES = new HashMap<>();
准备一些测试数据
/**
3、准备一些测试数据
*/
static {
Customer c = new Customer();
c.setLoginName("zyf888");
c.setPassWord("123456");
c.setUserName("黑马刘德华");
c.setSex('男');
c.setMoney(10000);
c.setPhone("110110");
ALL_USERS.add(c);
Customer c1 = new Customer();
c1.setLoginName("gzl888");
c1.setPassWord("123456");
c1.setUserName("黑马关之琳");
c1.setSex('女');
c1.setMoney(2000);
c1.setPhone("111111");
ALL_USERS.add(c1);
Business b = new Business();
b.setLoginName("baozugong888");
b.setPassWord("123456");
b.setUserName("黑马包租公");
b.setMoney(0);
b.setSex('男');
b.setPhone("110110");
b.setAddress("火星6号2B二层");
b.setShopName("甜甜圈国际影城");
ALL_USERS.add(b);
// 注意,商家一定需要加入到店铺排片信息中去
List<Movie> movies = new ArrayList<>();
ALL_MOVIES.put(b , movies); // b = []
Business b2 = new Business();
b2.setLoginName("baozupo888");
b2.setPassWord("123456");
b2.setUserName("黑马包租婆");
b2.setMoney(0);
b2.setSex('女');
b2.setPhone("110110");
b2.setAddress("火星8号8B八层");
b2.setShopName("巧克力国际影城");
ALL_USERS.add(b2);
// 注意,商家一定需要加入到店铺排片信息中去
List<Movie> movies3 = new ArrayList<>();
ALL_MOVIES.put(b2 , movies3); // b2 = []
}
4.3、登陆设计
- 首页需要包含登录,商家入驻,客户注册功能。
- 商家和客户可以共用一个登录功能。
- 判断登录成功的用户的真实类型,根据用户类型完成对应的操作界面设计。
定义扫描器
public static final Scanner SYS_SC = new Scanner(System.in);
首页展示功能
/**
首页展示
*/
private static void showMain() {
while (true) {
System.out.println("===============黑马电影首页=================");
System.out.println("1、登录");
System.out.println("2、用户注册");
System.out.println("3、商家注册");
System.out.println("请输入操作命令:");
String command = SYS_SC.nextLine();
switch (command) {
case "1":
// 登录了
login();
break;
case "2":
//
break;
case "3":
break;
default:
System.out.println("命令有误,请确认!");
}
}
}
定义一个静态的User类
实现商家和客户可以共用一个登录功能。
// 定义一个静态的User类型的变量记住当前登录成功的用户对象
public static User loginUser;
登录功能
/**
登录功能
*/
private static void login() {
while (true) {
System.out.println("请您输入登录名称:");
String loginName = SYS_SC.nextLine();
System.out.println("请您输入登录密码:");
String passWord = SYS_SC.nextLine();
// 1、根据登录名称查询用户对象。
User u = getUserByLoginName(loginName);
// 2、判断用户对象是否存在,存在说明登录名称正确了
if(u != null){
// 3、比对密码是否正确
if(u.getPassWord().equals(passWord)){
// 登录成功了:...
loginUser = u; // 记住登录成功的用户
LOGGER.info(u.getUserName() +"登录了系统~~~");
// 判断是用户登录的,还是商家登录的。
if(u instanceof Customer) {
// 当前登录的是普通用户
showCustomerMain();
}else {
// 当前登录的肯定是商家用户
showBusinessMain();
}
return;
}else {
System.out.println("密码有毛病~~");
}
}else {
System.out.println("登录名称错误,请确认");
}
}
}
根据登录名称查询用户对象
public static User getUserByLoginName(String loginName){
for (User user : ALL_USERS) {
// 判断这个用户的登录名称是否是我们想要的
if(user.getLoginName().equals(loginName)){
return user;
}
}
return null; // 查询此用户登录名称
}
判断登录成功的用户的真实类型,根据用户类型完成对应的操作界面设计。
客户操作界面
/**
客户操作界面
*/
private static void showCustomerMain() {
while (true) {
System.out.println("============黑马电影客户界面===================");
System.out.println(loginUser.getUserName() + (loginUser.getSex()=='男'? "先生":"女士" + "欢迎您进入系统" +
"\t余额:" + loginUser.getMoney()));
System.out.println("请您选择要操作的功能:");
System.out.println("1、展示全部影片信息功能:");
System.out.println("2、根据电影名称查询电影信息:");
System.out.println("3、评分功能:");
System.out.println("4、购票功能:");
System.out.println("5、退出系统:");
System.out.println("请输入您要操作的命令:");
String command = SYS_SC.nextLine();
switch (command){
case "1":
// 展示全部排片信息
showAllMovies();
break;
case "2":
break;
case "3":
// 评分功能
scoreMovie();
showAllMovies();
break;
case "4":
// 购票功能
buyMovie();
break;
case "5":
return; // 干掉方法
default:
System.out.println("不存在该命令!!");
break;
}
}
}
商家的后台操作界面
/**
商家的后台操作界面
*/
private static void showBusinessMain() {
while (true) {
System.out.println("============黑马电影商家界面===================");
System.out.println(loginUser.getUserName() + (loginUser.getSex()=='男'? "先生":"女士" + "欢迎您进入系统"));
System.out.println("1、展示详情:");
System.out.println("2、上架电影:");
System.out.println("3、下架电影:");
System.out.println("4、修改电影:");
System.out.println("5、退出:");
System.out.println("请输入您要操作的命令:");
String command = SYS_SC.nextLine();
switch (command){
case "1":
// 展示全部排片信息
showBusinessInfos();
break;
case "2":
// 上架电影信息
addMovie();
break;
case "3":
// 下架电影信息
deleteMovie();
break;
case "4":
// 修改电影信息
updateMovie();
break;
case "5":
System.out.println(loginUser.getUserName() +"请您下次再来啊~~~");
return; // 干掉方法
default:
System.out.println("不存在该命令!!");
break;
}
}
}
4.4、商家功能
- 展示本商家的信息和其排片情况。
- 提供影片上架功能:就是创建一个影片对象,存入到商家的集合中去。
- 退出,需要回到登录的首页。
- 提供影片下架功能:其实就是从商家的集合中删除影片对象。
- 影片修改功能:拿到需要修改的影片对象,修改里面的数据。
定义日期格式
public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
展示当前商家的信息
/**
展示商家的详细:展示当前商家的信息。
*/
private static void showBusinessInfos() {
System.out.println("================商家详情界面=================");
LOGGER.info(loginUser.getUserName() +"商家,正在看自己的详情~~~");
// 根据商家对象(就是登录的用户loginUser),作为Map集合的键 提取对应的值就是其排片信息 :Map<Business , List<Movie>> ALL_MOVIES
Business business = (Business) loginUser;
System.out.println(business.getShopName() + "\t\t电话:" + business.getPhone()
+ "\t\t地址:" + business.getAddress() + "\t\t余额:" + business.getMoney());
List<Movie> movies = ALL_MOVIES.get(business);
if(movies.size() > 0) {
System.out.println("片名\t\t\t主演\t\t时长\t\t评分\t\t票价\t\t余票数量\t\t放映时间");
for (Movie movie : movies) {
System.out.println(movie.getName()+"\t\t\t" + movie.getActor()+ "\t\t" + movie.getTime()
+ "\t\t" + movie.getScore() + "\t\t" + movie.getPrice() + "\t\t" + movie.getNumber() + "\t\t"
+ sdf.format(movie.getStartTime()));
}
}else {
System.out.println("您的店铺当前无片在放映~~~~");
}
}
提供影片上架功能
就是创建一个影片对象,存入到商家的集合中去。
/**
商家进行电影上架
Map<Business , List<Movie>> ALL_MOVIES
u1 = [p1,p2,p3]
u2 = [p1,p2,p3]
*/
private static void addMovie() {
System.out.println("================上架电影====================");
// 根据商家对象(就是登录的用户loginUser),作为Map集合的键 提取对应的值就是其排片信息 :Map<Business , List<Movie>> ALL_MOVIES
Business business = (Business) loginUser;
List<Movie> movies = ALL_MOVIES.get(business);
System.out.println("请您输入新片名:");
String name = SYS_SC.nextLine();
System.out.println("请您输入主演:");
String actor = SYS_SC.nextLine();
System.out.println("请您输入时长:");
String time = SYS_SC.nextLine();
System.out.println("请您输入票价:");
String price = SYS_SC.nextLine();
System.out.println("请您输入票数:");
String totalNumber = SYS_SC.nextLine(); // 200\n
while (true) {
try {
System.out.println("请您输入影片放映时间:");
String stime = SYS_SC.nextLine();
// public Movie(String name, String actor, double time, double price, int number, Date startTime) // 封装成电影对象 ,加入集合movices中去
Movie movie = new Movie(name, actor ,Double.valueOf(time) , Double.valueOf(price)
, Integer.valueOf(totalNumber) , sdf.parse(stime));
movies.add(movie);
System.out.println("您已经成功上架了:《" + movie.getName() + "》");
return; // 直接退出去
} catch (ParseException e) {
e.printStackTrace();
LOGGER.error("时间解析出了毛病");
}
}
}
退出功能
case "5":
System.out.println(loginUser.getUserName() +"请您下次再来啊~~~");
return; // 干掉方法
影片下架功能
/**
影片下架功能
*/
private static void deleteMovie() {
System.out.println("================下架电影====================");
Business business = (Business) loginUser;
List<Movie> movies = ALL_MOVIES.get(business);
if(movies.size() == 0) {
System.out.println("当期无片可以下架~~");
return;
}
// 2、让用户选择需要下架的电影名称
while (true) {
System.out.println("请您输入需要下架的电影名称:");
String movieName = SYS_SC.nextLine();
// 3、去查询有没有这个影片对象。
Movie movie = getMovieByName(movieName);
if(movie != null){
// 下架它
movies.remove(movie);
System.out.println("您当前店铺已经成功下架了:" + movie.getName());
showBusinessInfos();
return;
}else {
System.out.println("您的店铺没有上架该影片!");
System.out.println("请问继续下架吗?y/n");
String command = SYS_SC.nextLine();
switch (command) {
case "y":
break;
default:
System.out.println("好的!");
return;
}
}
}
}
查询当前商家下的排片
/**
去查询当前商家下的排片
*/
public static Movie getMovieByName(String movieName){
Business business = (Business) loginUser;
List<Movie> movies = ALL_MOVIES.get(business);
for (Movie movie : movies) {
if(movie.getName().contains(movieName)) {
return movie;
}
}
return null;
}
影片修改功能
/**
影片修改功能
*/
private static void updateMovie() {
System.out.println("================修改电影====================");
Business business = (Business) loginUser;
List<Movie> movies = ALL_MOVIES.get(business);
if(movies.size() == 0) {
System.out.println("当期无片可以修改~~");
return;
}
// 2、让用户选择需要下架的电影名称
while (true) {
System.out.println("请您输入需要修改的电影名称:");
String movieName = SYS_SC.nextLine();
// 3、去查询有没有这个影片对象。
Movie movie = getMovieByName(movieName);
if(movie != null){
// 修改它
System.out.println("请您输入修改后的片名:");
String name = SYS_SC.nextLine();
System.out.println("请您输入修改后主演:");
String actor = SYS_SC.nextLine();
System.out.println("请您输入修改后时长:");
String time = SYS_SC.nextLine();
System.out.println("请您输入修改后票价:");
String price = SYS_SC.nextLine();
System.out.println("请您输入修改后票数:");
String totalNumber = SYS_SC.nextLine(); // 200\n
while (true) {
try {
System.out.println("请您输入修改后的影片放映时间:");
String stime = SYS_SC.nextLine();
movie.setName(name);
movie.setActor(actor);
movie.setPrice(Double.valueOf(price));
movie.setTime(Double.valueOf(time));
movie.setNumber(Integer.valueOf(totalNumber));
movie.setStartTime(sdf.parse(stime));
System.out.println("恭喜您,您成功修改了该影片了!!!");
showBusinessInfos();
return; // 直接退出去
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("时间解析出了毛病");
}
}
}else {
System.out.println("您的店铺没有上架该影片!");
System.out.println("请问继续修改吗?y/n");
String command = SYS_SC.nextLine();
switch (command) {
case "y":
break;
default:
System.out.println("好的!");
return;
}
}
}
}
4.5、用户功能
- 展示全部影片:遍历全部商家和其排片信息并展示出来。
- 用户可以选择需要购买票的商家和其电影信息。
- 可以选择购买的数量。
- 购买成功后需要支付金额,并更新商家金额和客户金额
展示全部商家和其排片信息
/**
用户功能:展示全部商家和其排片信息
*/
private static void showAllMovies() {
System.out.println("=============展示全部商家排片信息=================");
ALL_MOVIES.forEach((business, movies) -> {
System.out.println(business.getShopName() + "\t\t电话:" + business.getPhone() + "\t\t地址:" + business.getAddress());
System.out.println("\t\t\t片名\t\t\t主演\t\t时长\t\t评分\t\t票价\t\t余票数量\t\t放映时间");
for (Movie movie : movies) {
System.out.println("\t\t\t" + movie.getName()+"\t\t\t" + movie.getActor()+ "\t\t" + movie.getTime()
+ "\t\t" + movie.getScore() + "\t\t" + movie.getPrice() + "\t\t" + movie.getNumber() + "\t\t"
+ sdf.format(movie.getStartTime()));
}
});
}
用户购票功能
/**
用户购票功能 ALL_MOVIES = {b1=[p1,p2,p3,..] , b2=[p2,p3,...]}
*/
private static void buyMovie() {
showAllMovies();
System.out.println("=============用户购票功能=================");
while (true) {
System.out.println("请您输入需要买票的门店:");
String shopName = SYS_SC.nextLine();
// 1、查询是否存在该商家。
Business business = getBusinessByShopName(shopName);
if(business == null){
System.out.println("对不起,没有该店铺!请确认");
}else {
// 2、此商家全部的排片
List<Movie> movies = ALL_MOVIES.get(business);
// 3、判断是否存在上映的电影
if(movies.size() > 0) {
// 4、开始进行选片购买
while (true) {
System.out.println("请您输入需要购买电影名称:");
String movieName = SYS_SC.nextLine();
// 去当前商家下,查询该电影对象。
Movie movie = getMovieByShopAndName(business, movieName);
if(movie != null){
// 开始购买
while (true) {
System.out.println("请您输入要购买的电影票数:");
String number = SYS_SC.nextLine();
int buyNumber = Integer.valueOf(number);
// 判断电影是否购票
if(movie.getNumber() >= buyNumber){
// 可以购买了
// 当前需要花费的金额
double money = BigDecimal.valueOf(movie.getPrice()).multiply(BigDecimal.valueOf(buyNumber))
.doubleValue();
if(loginUser.getMoney() >= money){
// 终于可以买票了
System.out.println("您成功购买了"+ movie.getName() + buyNumber +
"张票!总金额是:" + money);
// 更新自己的金额 更新商家的金额
loginUser.setMoney(loginUser.getMoney() - money);
business.setMoney(business.getMoney() + money);
movie.setNumber(movie.getNumber() - buyNumber);
Customer c = (Customer) loginUser;
// 记录购买电影的信息
// 第一个参数是购买的电影,第二个参数是没有评价的标记!
c.getBuyMovies().put(movie.getName(), false);
return;// 结束方法
}else {
// 钱不够!
System.out.println("是否继续~~");
System.out.println("是否继续买票?y/n");
String command = SYS_SC.nextLine();
switch (command) {
case "y":
break;
default:
System.out.println("好的!");
return;
}
}
}else {
// 票数不够
System.out.println("您当前最多可以购买:" + movie.getNumber());
System.out.println("是否继续买票?y/n");
String command = SYS_SC.nextLine();
switch (command) {
case "y":
break;
default:
System.out.println("好的!");
return;
}
}
}
}else {
System.out.println("电影名称有毛病~~");
}
}
}else {
System.out.println("该电影院关门了~~~");
System.out.println("是否继续买票?y/n");
String command = SYS_SC.nextLine();
switch (command) {
case "y":
break;
default:
System.out.println("好的!");
return;
}
}
}
}
}
根据商家店铺名称查询商家对象
/**
根据商家店铺名称查询商家对象
* @return
*/
public static Business getBusinessByShopName(String shopName){
Set<Business> businesses = ALL_MOVIES.keySet();
for (Business business : businesses) {
if(business.getShopName().equals(shopName)){
return business;
}
}
return null;
}
根据商家和电影名称查询电影对象
public static Movie getMovieByShopAndName(Business business , String name){
List<Movie> movies = ALL_MOVIES.get(business);
for (Movie movie : movies) {
if(movie.getName().contains(name)){
return movie;
}
}
return null;
}
4.6、代码整理
Movie 类
public class Business extends User{
// 店铺名称
private String shopName;
// 店铺地址
private String address;
public String getShopName() {
return shopName;
}
public void setShopName(String shopName) {
this.shopName = shopName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
User 类
/**
用户类(客户和商家的父类 )
*/
public class User {
private String loginName; // 假名 不能重复
private String userName; // 真名
private String passWord;
private char sex;
private String phone;
private double money;
public User(){
}
public User(String loginName, String userName, String passWord, char sex, String phone, double money) {
this.loginName = loginName;
this.userName = userName;
this.passWord = passWord;
this.sex = sex;
this.phone = phone;
this.money = money;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
Business 类
public class Business extends User{
// 店铺名称
private String shopName;
// 店铺地址
private String address;
public String getShopName() {
return shopName;
}
public void setShopName(String shopName) {
this.shopName = shopName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Customer 类
import java.util.HashMap;
import java.util.Map;
/**
客户角色
*/
public class Customer extends User{
// 定义一个属性存储购买记录。
private Map<String, Boolean> buyMovies = new HashMap<>();
public Map<String, Boolean> getBuyMovies() {
return buyMovies;
}
public void setBuyMovies(Map<String, Boolean> buyMovies) {
this.buyMovies = buyMovies;
}
}
功能实现类
import com.itheima.bean.Business;
import com.itheima.bean.Customer;
import com.itheima.bean.Movie;
import com.itheima.bean.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
public class MovieSystem {
/**
定义系统的数据容器用户存储数据
1、存储很多用户(客户对象,商家对象)
*/
public static final List<User> ALL_USERS = new ArrayList<>();
/**
2、存储系统全部商家和其排片信息 。
商家1 = [p1,p2,p3,...]
商家2 = [p2,p3,...]
...
*/
public static final Map<Business, List<Movie>> ALL_MOVIES = new HashMap<>();
public static final Scanner SYS_SC = new Scanner(System.in);
// 定义一个静态的User类型的变量记住当前登录成功的用户对象
public static User loginUser;
public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
public static final Logger LOGGER = LoggerFactory.getLogger("MovieSystem.class");
/**
3、准备一些测试数据
*/
static {
Customer c = new Customer();
c.setLoginName("zyf888");
c.setPassWord("123456");
c.setUserName("黑马刘德华");
c.setSex('男');
c.setMoney(10000);
c.setPhone("110110");
ALL_USERS.add(c);
Customer c1 = new Customer();
c1.setLoginName("gzl888");
c1.setPassWord("123456");
c1.setUserName("黑马关之琳");
c1.setSex('女');
c1.setMoney(2000);
c1.setPhone("111111");
ALL_USERS.add(c1);
Business b = new Business();
b.setLoginName("baozugong888");
b.setPassWord("123456");
b.setUserName("黑马包租公");
b.setMoney(0);
b.setSex('男');
b.setPhone("110110");
b.setAddress("火星6号2B二层");
b.setShopName("甜甜圈国际影城");
ALL_USERS.add(b);
// 注意,商家一定需要加入到店铺排片信息中去
List<Movie> movies = new ArrayList<>();
ALL_MOVIES.put(b , movies); // b = []
Business b2 = new Business();
b2.setLoginName("baozupo888");
b2.setPassWord("123456");
b2.setUserName("黑马包租婆");
b2.setMoney(0);
b2.setSex('女');
b2.setPhone("110110");
b2.setAddress("火星8号8B八层");
b2.setShopName("巧克力国际影城");
ALL_USERS.add(b2);
// 注意,商家一定需要加入到店铺排片信息中去
List<Movie> movies3 = new ArrayList<>();
ALL_MOVIES.put(b2 , movies3); // b2 = []
}
public static void main(String[] args) {
showMain();
}
/**
首页展示
*/
private static void showMain() {
while (true) {
System.out.println("===============黑马电影首页=================");
System.out.println("1、登录");
System.out.println("2、用户注册");
System.out.println("3、商家注册");
System.out.println("请输入操作命令:");
String command = SYS_SC.nextLine();
switch (command) {
case "1":
// 登录了
login();
break;
case "2":
//
break;
case "3":
break;
default:
System.out.println("命令有误,请确认!");
}
}
}
/**
登录功能
*/
private static void login() {
while (true) {
System.out.println("请您输入登录名称:");
String loginName = SYS_SC.nextLine();
System.out.println("请您输入登录密码:");
String passWord = SYS_SC.nextLine();
// 1、根据登录名称查询用户对象。
User u = getUserByLoginName(loginName);
// 2、判断用户对象是否存在,存在说明登录名称正确了
if(u != null){
// 3、比对密码是否正确
if(u.getPassWord().equals(passWord)){
// 登录成功了:...
loginUser = u; // 记住登录成功的用户
LOGGER.info(u.getUserName() +"登录了系统~~~");
// 判断是用户登录的,还是商家登录的。
if(u instanceof Customer) {
// 当前登录的是普通用户
showCustomerMain();
}else {
// 当前登录的肯定是商家用户
showBusinessMain();
}
return;
}else {
System.out.println("密码有毛病~~");
}
}else {
System.out.println("登录名称错误,请确认");
}
}
}
/**
商家的后台操作界面
*/
private static void showBusinessMain() {
while (true) {
System.out.println("============黑马电影商家界面===================");
System.out.println(loginUser.getUserName() + (loginUser.getSex()=='男'? "先生":"女士" + "欢迎您进入系统"));
System.out.println("1、展示详情:");
System.out.println("2、上架电影:");
System.out.println("3、下架电影:");
System.out.println("4、修改电影:");
System.out.println("5、退出:");
System.out.println("请输入您要操作的命令:");
String command = SYS_SC.nextLine();
switch (command){
case "1":
// 展示全部排片信息
showBusinessInfos();
break;
case "2":
// 上架电影信息
addMovie();
break;
case "3":
// 下架电影信息
deleteMovie();
break;
case "4":
// 修改电影信息
updateMovie();
break;
case "5":
System.out.println(loginUser.getUserName() +"请您下次再来啊~~~");
return; // 干掉方法
default:
System.out.println("不存在该命令!!");
break;
}
}
}
/**
影片修改功能
*/
private static void updateMovie() {
System.out.println("================修改电影====================");
Business business = (Business) loginUser;
List<Movie> movies = ALL_MOVIES.get(business);
if(movies.size() == 0) {
System.out.println("当期无片可以修改~~");
return;
}
// 2、让用户选择需要下架的电影名称
while (true) {
System.out.println("请您输入需要修改的电影名称:");
String movieName = SYS_SC.nextLine();
// 3、去查询有没有这个影片对象。
Movie movie = getMovieByName(movieName);
if(movie != null){
// 修改它
System.out.println("请您输入修改后的片名:");
String name = SYS_SC.nextLine();
System.out.println("请您输入修改后主演:");
String actor = SYS_SC.nextLine();
System.out.println("请您输入修改后时长:");
String time = SYS_SC.nextLine();
System.out.println("请您输入修改后票价:");
String price = SYS_SC.nextLine();
System.out.println("请您输入修改后票数:");
String totalNumber = SYS_SC.nextLine(); // 200\n
while (true) {
try {
System.out.println("请您输入修改后的影片放映时间:");
String stime = SYS_SC.nextLine();
movie.setName(name);
movie.setActor(actor);
movie.setPrice(Double.valueOf(price));
movie.setTime(Double.valueOf(time));
movie.setNumber(Integer.valueOf(totalNumber));
movie.setStartTime(sdf.parse(stime));
System.out.println("恭喜您,您成功修改了该影片了!!!");
showBusinessInfos();
return; // 直接退出去
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("时间解析出了毛病");
}
}
}else {
System.out.println("您的店铺没有上架该影片!");
System.out.println("请问继续修改吗?y/n");
String command = SYS_SC.nextLine();
switch (command) {
case "y":
break;
default:
System.out.println("好的!");
return;
}
}
}
}
/**
影片下架功能
*/
private static void deleteMovie() {
System.out.println("================下架电影====================");
Business business = (Business) loginUser;
List<Movie> movies = ALL_MOVIES.get(business);
if(movies.size() == 0) {
System.out.println("当期无片可以下架~~");
return;
}
// 2、让用户选择需要下架的电影名称
while (true) {
System.out.println("请您输入需要下架的电影名称:");
String movieName = SYS_SC.nextLine();
// 3、去查询有没有这个影片对象。
Movie movie = getMovieByName(movieName);
if(movie != null){
// 下架它
movies.remove(movie);
System.out.println("您当前店铺已经成功下架了:" + movie.getName());
showBusinessInfos();
return;
}else {
System.out.println("您的店铺没有上架该影片!");
System.out.println("请问继续下架吗?y/n");
String command = SYS_SC.nextLine();
switch (command) {
case "y":
break;
default:
System.out.println("好的!");
return;
}
}
}
}
/**
去查询当前商家下的排片
*/
public static Movie getMovieByName(String movieName){
Business business = (Business) loginUser;
List<Movie> movies = ALL_MOVIES.get(business);
for (Movie movie : movies) {
if(movie.getName().contains(movieName)) {
return movie;
}
}
return null;
}
/**
商家进行电影上架
Map<Business , List<Movie>> ALL_MOVIES
u1 = [p1,p2,p3]
u2 = [p1,p2,p3]
*/
private static void addMovie() {
System.out.println("================上架电影====================");
// 根据商家对象(就是登录的用户loginUser),作为Map集合的键 提取对应的值就是其排片信息 :Map<Business , List<Movie>> ALL_MOVIES
Business business = (Business) loginUser;
List<Movie> movies = ALL_MOVIES.get(business);
System.out.println("请您输入新片名:");
String name = SYS_SC.nextLine();
System.out.println("请您输入主演:");
String actor = SYS_SC.nextLine();
System.out.println("请您输入时长:");
String time = SYS_SC.nextLine();
System.out.println("请您输入票价:");
String price = SYS_SC.nextLine();
System.out.println("请您输入票数:");
String totalNumber = SYS_SC.nextLine(); // 200\n
while (true) {
try {
System.out.println("请您输入影片放映时间:");
String stime = SYS_SC.nextLine();
// public Movie(String name, String actor, double time, double price, int number, Date startTime) // 封装成电影对象 ,加入集合movices中去
Movie movie = new Movie(name, actor ,Double.valueOf(time) , Double.valueOf(price)
, Integer.valueOf(totalNumber) , sdf.parse(stime));
movies.add(movie);
System.out.println("您已经成功上架了:《" + movie.getName() + "》");
return; // 直接退出去
} catch (ParseException e) {
e.printStackTrace();
LOGGER.error("时间解析出了毛病");
}
}
}
/**
定义一个静态的Map集合存储电影的评分
*/
public static final Map<String , List<Double>> MOVIES_SCORE = new HashMap<>();
/**
展示商家的详细:展示当前商家的信息。
*/
private static void showBusinessInfos() {
System.out.println("================商家详情界面=================");
LOGGER.info(loginUser.getUserName() +"商家,正在看自己的详情~~~");
// 根据商家对象(就是登录的用户loginUser),作为Map集合的键 提取对应的值就是其排片信息 :Map<Business , List<Movie>> ALL_MOVIES
Business business = (Business) loginUser;
System.out.println(business.getShopName() + "\t\t电话:" + business.getPhone()
+ "\t\t地址:" + business.getAddress() + "\t\t余额:" + business.getMoney());
List<Movie> movies = ALL_MOVIES.get(business);
if(movies.size() > 0) {
System.out.println("片名\t\t\t主演\t\t时长\t\t评分\t\t票价\t\t余票数量\t\t放映时间");
for (Movie movie : movies) {
System.out.println(movie.getName()+"\t\t\t" + movie.getActor()+ "\t\t" + movie.getTime()
+ "\t\t" + movie.getScore() + "\t\t" + movie.getPrice() + "\t\t" + movie.getNumber() + "\t\t"
+ sdf.format(movie.getStartTime()));
}
}else {
System.out.println("您的店铺当前无片在放映~~~~");
}
}
/**
客户操作界面
*/
private static void showCustomerMain() {
while (true) {
System.out.println("============黑马电影客户界面===================");
System.out.println(loginUser.getUserName() + (loginUser.getSex()=='男'? "先生":"女士" + "欢迎您进入系统" +
"\t余额:" + loginUser.getMoney()));
System.out.println("请您选择要操作的功能:");
System.out.println("1、展示全部影片信息功能:");
System.out.println("2、根据电影名称查询电影信息:");
System.out.println("3、评分功能:");
System.out.println("4、购票功能:");
System.out.println("5、退出系统:");
System.out.println("请输入您要操作的命令:");
String command = SYS_SC.nextLine();
switch (command){
case "1":
// 展示全部排片信息
showAllMovies();
break;
case "2":
break;
case "3":
// 评分功能
scoreMovie();
showAllMovies();
break;
case "4":
// 购票功能
buyMovie();
break;
case "5":
return; // 干掉方法
default:
System.out.println("不存在该命令!!");
break;
}
}
}
private static void scoreMovie() {
// 1、查询当前登录成功的用户历史购买记录,看哪些电影是它可以评分的。
Customer c = (Customer) loginUser;
Map<String, Boolean> movies = c.getBuyMovies();
if(movies.size() == 0 ){
System.out.println("当前您没有看过电影,不能评价!");
return;
}
// 买过了 ,看哪些电影是它可以评分的。
movies.forEach((name, flag) -> {
if(flag){
System.out.println(name +"此电影已评价");
}else {
System.out.println("请您对:" + name +"进行打分(0-10):");
double score = Double.valueOf(SYS_SC.nextLine());
// 先根据电影名称拿到评分数据
List<Double> scores = MOVIES_SCORE.get(name); // MOVIES_SCORE = [ 名称=[10] , ... ]
if(scores == null){
// 说明此电影是第一次评价
scores = new ArrayList<>();
scores.add(score);
MOVIES_SCORE.put(name , scores);
}else {
scores.add(score);
}
movies.put(name, true);
}
});
}
/**
用户购票功能 ALL_MOVIES = {b1=[p1,p2,p3,..] , b2=[p2,p3,...]}
*/
private static void buyMovie() {
showAllMovies();
System.out.println("=============用户购票功能=================");
while (true) {
System.out.println("请您输入需要买票的门店:");
String shopName = SYS_SC.nextLine();
// 1、查询是否存在该商家。
Business business = getBusinessByShopName(shopName);
if(business == null){
System.out.println("对不起,没有该店铺!请确认");
}else {
// 2、此商家全部的排片
List<Movie> movies = ALL_MOVIES.get(business);
// 3、判断是否存在上映的电影
if(movies.size() > 0) {
// 4、开始进行选片购买
while (true) {
System.out.println("请您输入需要购买电影名称:");
String movieName = SYS_SC.nextLine();
// 去当前商家下,查询该电影对象。
Movie movie = getMovieByShopAndName(business, movieName);
if(movie != null){
// 开始购买
while (true) {
System.out.println("请您输入要购买的电影票数:");
String number = SYS_SC.nextLine();
int buyNumber = Integer.valueOf(number);
// 判断电影是否购票
if(movie.getNumber() >= buyNumber){
// 可以购买了
// 当前需要花费的金额
double money = BigDecimal.valueOf(movie.getPrice()).multiply(BigDecimal.valueOf(buyNumber))
.doubleValue();
if(loginUser.getMoney() >= money){
// 终于可以买票了
System.out.println("您成功购买了"+ movie.getName() + buyNumber +
"张票!总金额是:" + money);
// 更新自己的金额 更新商家的金额
loginUser.setMoney(loginUser.getMoney() - money);
business.setMoney(business.getMoney() + money);
movie.setNumber(movie.getNumber() - buyNumber);
Customer c = (Customer) loginUser;
// 记录购买电影的信息
// 第一个参数是购买的电影,第二个参数是没有评价的标记!
c.getBuyMovies().put(movie.getName(), false);
return;// 结束方法
}else {
// 钱不够!
System.out.println("是否继续~~");
System.out.println("是否继续买票?y/n");
String command = SYS_SC.nextLine();
switch (command) {
case "y":
break;
default:
System.out.println("好的!");
return;
}
}
}else {
// 票数不够
System.out.println("您当前最多可以购买:" + movie.getNumber());
System.out.println("是否继续买票?y/n");
String command = SYS_SC.nextLine();
switch (command) {
case "y":
break;
default:
System.out.println("好的!");
return;
}
}
}
}else {
System.out.println("电影名称有毛病~~");
}
}
}else {
System.out.println("该电影院关门了~~~");
System.out.println("是否继续买票?y/n");
String command = SYS_SC.nextLine();
switch (command) {
case "y":
break;
default:
System.out.println("好的!");
return;
}
}
}
}
}
public static Movie getMovieByShopAndName(Business business , String name){
List<Movie> movies = ALL_MOVIES.get(business);
for (Movie movie : movies) {
if(movie.getName().contains(name)){
return movie;
}
}
return null;
}
/**
根据商家店铺名称查询商家对象
* @return
*/
public static Business getBusinessByShopName(String shopName){
Set<Business> businesses = ALL_MOVIES.keySet();
for (Business business : businesses) {
if(business.getShopName().equals(shopName)){
return business;
}
}
return null;
}
/**
用户功能:展示全部商家和其排片信息
*/
private static void showAllMovies() {
System.out.println("=============展示全部商家排片信息=================");
ALL_MOVIES.forEach((business, movies) -> {
System.out.println(business.getShopName() + "\t\t电话:" + business.getPhone() + "\t\t地址:" + business.getAddress());
System.out.println("\t\t\t片名\t\t\t主演\t\t时长\t\t评分\t\t票价\t\t余票数量\t\t放映时间");
for (Movie movie : movies) {
System.out.println("\t\t\t" + movie.getName()+"\t\t\t" + movie.getActor()+ "\t\t" + movie.getTime()
+ "\t\t" + movie.getScore() + "\t\t" + movie.getPrice() + "\t\t" + movie.getNumber() + "\t\t"
+ sdf.format(movie.getStartTime()));
}
});
}
public static User getUserByLoginName(String loginName){
for (User user : ALL_USERS) {
// 判断这个用户的登录名称是否是我们想要的
if(user.getLoginName().equals(loginName)){
return user;
}
}
return null; // 查询此用户登录名称
}
}
5、File 类
5.1、File 类概述
- File类在包java.io.File下、代表操作系统的文件对象(文件、文件夹)。
- File类提供了诸如:定位文件,获取文件本身的信息、删除文件、创建文件(文件夹)等功能。
File类创建对象
方法名称 | 说明 |
---|---|
public File(String pathname) | 根据文件路径创建文件对象 |
public File(String parent, String child) | 从父路径名字符串和子路径名字符串创建文件对象 |
public File(File parent, String child) | 根据父路径对应文件对象和子路径名字符串创建文件对象 |
- File对象可以定位文件和文件夹
- File封装的对象仅仅是一个路径名,这个路径可以是存在的,也可以是不存在的。
绝对路径和相对路径
-
绝对路径:从盘符开始
File file1 = new File(“D:\\itheima\\a.txt”);
-
相对路径:不带盘符,默认直接到当前工程下的目录寻找文件。
File file3 = new File(“模块名\\a.txt”);
代码演示
import java.io.File;
/**
目标:学会创建File对象定位操作系统的文件(文件 文件夹的)
*/
public class FileDemo {
public static void main(String[] args) {
// 1、创建File对象(指定了文件的路径)
// 路径写法: D:\resources\xueshan.jpeg
// D:/resources/xueshan.jpeg
// File.separator
// File f = new File("D:\\resources\\xueshan.jpeg");
// File f = new File("D:/resources/xueshan.jpeg");
File f = new File("D:" + File.separator+"resources"+ File.separator +"xueshan.jpeg");
long size = f.length(); // 是文件的字节大小
System.out.println(size);
// 2、File创建对象,支持绝对路径 支持相对路径(重点)
File f1 = new File("D:\\resources\\beauty.jpeg"); // 绝对路径
System.out.println(f1.length());
// 相对路径:一般定位模块中的文件的。 相对到工程下!!
File f2 = new File("file-io-app/src/com/itheima/data.txt");
System.out.println(f2.length());
// 3、File创建对象 ,可以是文件也可以是文件夹
File f3 = new File("D:\\resources");
System.out.println(f3.exists()); // 判断这个路径是否存在,这个文件夹存在否
}
}
5.2、File类的常用API
5.2.1、判断文件类型、获取文件信息
方法名称 | 说明 |
---|---|
public boolean isDirectory() | 测试此抽象路径名表示的File是否为文件夹 |
public boolean isFile() | 测试此抽象路径名表示的File是否为文件 |
public boolean exists() | 测试此抽象路径名表示的File是否存在 |
public String getAbsolutePath() | 返回此抽象路径名的绝对路径名字符串 |
public String getPath() | 将此抽象路径名转换为路径名字符串 |
public String getName() | 返回由此抽象路径名表示的文件或文件夹的名称 |
public long lastModified() | 返回文件最后修改的时间毫秒值 |
代码演示
import java.io.File;
import java.text.SimpleDateFormat;
/**
目标:File类的获取功能的API
- public String getAbsolutePath() :返回此File的绝对路径名字符串。
- public String getPath() : 获取创建文件对象的时候用的路径
- public String getName() : 返回由此File表示的文件或目录的名称。
- public long length() : 返回由此File表示的文件的长度。
*/
public class FileDemo02 {
public static void main(String[] args) {
// 1.绝对路径创建一个文件对象
File f1 = new File("D:/resources/xueshan.jpeg");
// a.获取它的绝对路径。
System.out.println(f1.getAbsolutePath());
// b.获取文件定义的时候使用的路径。
System.out.println(f1.getPath());
// c.获取文件的名称:带后缀。
System.out.println(f1.getName());
// d.获取文件的大小:字节个数。
System.out.println(f1.length()); // 字节大小
// e.获取文件的最后修改时间
long time = f1.lastModified();
System.out.println("最后修改时间:" + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(time));
// f、判断文件是文件还是文件夹
System.out.println(f1.isFile()); // true
System.out.println(f1.isDirectory()); // false
System.out.println("-------------------------");
File f2 = new File("file-io-app\\src\\data.txt");
// a.获取它的绝对路径。
System.out.println(f2.getAbsolutePath());
// b.获取文件定义的时候使用的路径。
System.out.println(f2.getPath());
// c.获取文件的名称:带后缀。
System.out.println(f2.getName());
// d.获取文件的大小:字节个数。
System.out.println(f2.length()); // 字节大小
// e.获取文件的最后修改时间
long time1 = f2.lastModified();
System.out.println("最后修改时间:" + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(time1));
// f、判断文件是文件还是文件夹
System.out.println(f2.isFile()); // true
System.out.println(f2.isDirectory()); // false
System.out.println(f2.exists()); // true
File file = new File("D:/");
System.out.println(file.isFile()); // false
System.out.println(file.isDirectory()); // true
System.out.println(file.exists()); // true
File file1 = new File("D:/aaa");
System.out.println(file1.isFile()); // false
System.out.println(file1.isDirectory()); // false
System.out.println(file1.exists()); // false
}
}
5.2.2、创建文件、删除文件功能
File类创建文件的功能
方法名称 | 说明 |
---|---|
public boolean createNewFile() | 创建一个新的空的文件 |
public boolean mkdir() | 只能创建一级文件夹 |
public boolean mkdirs() | 可以创建多级文件夹 |
File类删除文件的功能
方法名称 | 说明 |
---|---|
public boolean delete() | 删除由此抽象路径名表示的文件或空文件夹 |
- delete方法默认只能删除文件和空文件夹。
- delete方法直接删除不走回收站
代码演示
import java.io.File;
import java.io.IOException;
/**
目标:File类的创建和删除的方法
- public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,
创建一个新的空文件。 (几乎不用的,因为以后文件都是自动创建的!)
- public boolean delete() :删除由此File表示的文件或目录。 (只能删除空目录)
- public boolean mkdir() :创建由此File表示的目录。(只能创建一级目录)
- public boolean mkdirs() :可以创建多级目录(建议使用的)
*/
public class FileDemo03 {
public static void main(String[] args) throws IOException {
File f = new File("file-io-app\\src\\com\\itheima\\data.txt");
// a.创建新文件,创建成功返回true ,反之 ,不需要这个,以后文件写出去的时候都会自动创建
System.out.println(f.createNewFile());
File f1 = new File("file-io-app\\src\\com\\itheima\\data.txt");
System.out.println(f1.createNewFile()); // (几乎不用的,因为以后文件都是自动创建的!)
// b.mkdir创建一级目录
File f2 = new File("D:/resources/aaa");
System.out.println(f2.mkdir());
// c.mkdirs创建多级目录(重点)
File f3 = new File("D:/resources/ccc/ddd/eee/ffff");
// System.out.println(f3.mkdir());
System.out.println(f3.mkdirs()); // 支持多级创建
// d.删除文件或者空文件夹
System.out.println(f1.delete());
File f4 = new File("D:/resources/xueshan.jpeg");
System.out.println(f4.delete()); // 占用一样可以删除
// 只能删除空文件夹,不能删除非空文件夹.
File f5 = new File("D:/resources/aaa");
System.out.println(f5.delete());
}
}
5.2.3、遍历文件夹
方法名称 | 说明 |
---|---|
public String[] list() | 获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。 |
public File[] listFiles()(常用) | 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点) |
listFiles方法注意事项
- 当调用者不存在时,返回null
- 当调用者是一个文件时,返回null
- 当调用者是一个空文件夹时,返回一个长度为0的数组
- 当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回
- 当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏内容
- 当调用者是一个需要权限才能进入的文件夹时,返回null
代码演示
import java.io.File;
import java.util.Arrays;
/**
目标:File针对目录的遍历
- public String[] list():
获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。
- public File[] listFiles()(常用):
获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
*/
public class FileDemo04 {
public static void main(String[] args) {
// 1、定位一个目录
File f1 = new File("D:/resources");
String[] names = f1.list();
for (String name : names) {
System.out.println(name);
}
// 2.一级文件对象
// 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
File[] files = f1.listFiles();
for (File f : files) {
System.out.println(f.getAbsolutePath());
}
}
}
6、方法递归
6.1、递归的形式和特点
什么是方法递归?
- 方法直接调用自己或者间接调用自己的形式称为方法递归( recursion)。
- 递归做为一种算法在程序设计语言中广泛应用。
递归的形式
- 直接递归:方法自己调用自己。
- 间接递归:方法调用其他方法,其他方法又回调方法自己。
方法递归存在的问题?
- 递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出现象。
代码演示
/**
递归的形式
*/
public class RecursionDemo01 {
public static void main(String[] args) {
test2();
}
public static void test(){
System.out.println("=======test被执行========");
test(); // 方法递归 直接递归形式
}
public static void test2(){
System.out.println("=======test2被执行========");
test3(); // 方法递归 间接递归
}
private static void test3() {
System.out.println("=======test3被执行========");
test2();
}
}
6.2、递归的算法流程、核心要素
递归案例-计算1-n的阶乘
代码演示
测试代码
/**
目标:递归的算法和执行流程
*/
public class RecursionDemo02 {
public static void main(String[] args) {
System.out.println(f(5));
}
public static int f(int n){
if(n == 1){
return 1;
}else {
return f(n - 1) * n;
}
}
}
输出结果
120
递归解决问题的思路
- 把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
递归算法三要素大体
-
递归的公式: f(n) = f(n-1) * n
-
递归的终结点:f(1)
-
递归的方向必须走向终结点:
f(5) = f(4) * 5f(4) = f(3) * 4
f(3) = f(2) * 3
f(2) = f(1) * 2
f(1) = 1
递归求阶乘的执行流程
6.3、递归常见案例
6.3.1、计算1-n的和
代码演示
测试代码
/**
目标:1 - n求和
*/
public class RecursionDemo03 {
public static void main(String[] args) {
System.out.println(f(5));
}
public static int f(int n){
if(n == 1){
return 1;
}else {
return f(n - 1) + n;
}
}
}
输出结果
15
6.3.2、猴子吃桃问题
- 猴子第一天摘下若干桃子,当即吃了一半,觉得好不过瘾,于是又多吃了一个
- 第二天又吃了前天剩余桃子数量的一半,觉得好不过瘾,于是又多吃了一个
- 以后每天都是吃前天剩余桃子数量的一半,觉得好不过瘾,又多吃了一个
- 等到第10天的时候发现桃子只有1个了。
- 请问猴子第一天摘了多少个桃子?
分析:
整体来看,每一天都是做同一个事件,典型的规律化问题,考虑递归三要素:
-
递归公式:
f(x) - f(x)/2 - 1 = f(x+1)
2f(x) - f(x) - 2 = 2f(x + 1)
f(x) = 2f(x + 1) + 2
-
递归终结点:f(10) = 1
-
递归方向:略
代码演示
测试代码
/**
目标 猴子吃桃。
公式(合理的): f(x) - f(x)/2 - 1 = f(x+1)
2f(x) - f(x) - 2 = 2f(x + 1)
f(x) = 2f(x + 1) + 2
求f(1) = ?
终结点: f(10) = 1
递归的方向:合理的
*/
public class RecursionDemo04 {
public static void main(String[] args) {
System.out.println(f(1));
System.out.println(f(2));
System.out.println(f(3));
}
public static int f(int n){
if(n == 10){
return 1;
}else {
return 2 * f(n + 1) + 2;
}
}
}
输出结果
1534
766
382
6.4、非规律化递归案例
6.4.1、文件搜索
需求:
- 文件搜索、从C:盘中,搜索出某个文件名称并输出绝对路径。
分析:
- 先定位出的应该是一级文件对象
- 遍历全部一级文件对象,判断是否是文件
- 如果是文件,判断是否是自己想要的
- 如果是文件夹,需要继续递归进去重复上述过程
代码演示
测试代码
import java.io.File;
import java.io.IOException;
/**
目标:去D判断搜索 idea64.exe文件
*/
public class RecursionDemo05 {
public static void main(String[] args) {
// 2、传入目录 和 文件名称
searchFile(new File("D:/") , "Typora.exe");
}
/**
* 1、搜索某个目录下的全部文件,找到我们想要的文件。
* @param dir 被搜索的源目录
* @param fileName 被搜索的文件名称
*/
public static void searchFile(File dir,String fileName){
// 3、判断dir是否是目录
if(dir != null && dir.isDirectory()){
// 可以找了
// 4、提取当前目录下的一级文件对象
File[] files = dir.listFiles(); // null []
// 5、判断是否存在一级文件对象,存在才可以遍历
if(files != null && files.length > 0) {
for (File file : files) {
// 6、判断当前遍历的一级文件对象是文件 还是 目录
if(file.isFile()){
// 7、是不是咱们要找的,是把其路径输出即可
if(file.getName().contains(fileName)){
System.out.println("找到了:" + file.getAbsolutePath());
// 启动它。
try {
Runtime r = Runtime.getRuntime();
r.exec(file.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
}
}else {
// 8、是文件夹,需要继续递归寻找
searchFile(file, fileName);
}
}
}
}else {
System.out.println("对不起,当前搜索的位置不是文件夹!");
}
}
}
输出结果
找到了:D:\DevelopSoftware\Typora\Typora.exe
6.4.2、啤酒问题
需求:
- 啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶。
- 请问10元钱可以喝多少瓶酒,剩余多少空瓶和盖子。
/**
目标:啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,
请问10元钱可以喝多少瓶酒,剩余多少空瓶和盖子。
答案:15瓶 3盖子 1瓶子
*/
public class RecursionDemo06 {
// 定义一个静态的成员变量用于存储可以买的酒数量
public static int totalNumber; // 总数量
public static int lastBottleNumber; // 记录每次剩余的瓶子个数
public static int lastCoverNumber; // 记录每次剩余的盖子个数
public static void main(String[] args) {
// 1、拿钱买酒
buy(10);
System.out.println("总数:" + totalNumber);
System.out.println("剩余盖子数:" + lastCoverNumber);
System.out.println("剩余瓶子数:" + lastBottleNumber);
}
public static void buy(int money){
// 2、看可以立马买多少瓶
int buyNumber = money / 2; // 5
totalNumber += buyNumber;
// 3、把盖子 和瓶子换算成钱
// 统计本轮总的盖子数 和 瓶子数
int coverNumber = lastCoverNumber + buyNumber;
int bottleNumber = lastBottleNumber + buyNumber;
// 统计可以换算的钱
int allMoney = 0;
if(coverNumber >= 4){
allMoney += (coverNumber / 4) * 2;
}
lastCoverNumber = coverNumber % 4;
if(bottleNumber >= 2){
allMoney += (bottleNumber / 2) * 2;
}
lastBottleNumber = bottleNumber % 2;
if(allMoney >= 2){
buy(allMoney);
}
}
}
6.4.3、删除非空文件夹
需求:
- 删除非空文件夹
分析:
- File默认不可以删除非空文件夹
- 我们需要遍历文件夹,先删除里面的内容,再删除自己。
测试代码
import java.io.File;
/**
目标:删除非空文件夹
*/
public class RecursionDemo07 {
public static void main(String[] args) {
deleteDir(new File("D:/new"));
}
/**
删除文件夹,无所谓里面是否有内容,都可以删除
* @param dir
*/
public static void deleteDir(File dir){
// 1、判断dir存在且是文件夹
if(dir != null && dir.exists() && dir.isDirectory()){
// 2、提取一级文件对象。
File[] files = dir.listFiles();
// 3、判断是否存在一级文件对象,存在则遍历全部的一级文件对象去删除
if(files != null && files.length > 0){
// 里面有内容
for (File file : files) {
// 4、判断file是文件还是文件夹,文件直接删除
if(file.isFile()){
file.delete();
}else {
// 递归删除
deleteDir(file);
}
}
}
// 删除自己
dir.delete();
}
}
}
7、字符集
7.1、常见字符集介绍
字符集基础知识
-
计算机底层不可以直接存储字符的。计算机中底层只能存储二进制(0、1)
-
二进制是可以转换成十进制的
11 = 1*2^1 + 1*2^0 = 2 + 1 = 3 10 = 1*2^1 + 0*2^0 = 2 + 0 = 2 01 = 0*2^1 + 1*2^0 = 0 + 1 = 1 00 = 0*2^1 + 0*2^0 = 0 + 0 = 0 01100001 = 97 01100010 = 98
-
计算机底层可以表示十进制编号。计算机可以给人类字符进行编号存储,这套编号规则就是字符集。
ASCII 字符集
- ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字、英文、符号。
- ASCII使用1个字节存储一个字符,一个字节是8位,总共可以表示128个字符信息,对于英文,数字来说是够用的。
01100001 = 97 => a
01100010 = 98 => b
GBK
-
window系统默认的码表。兼容ASCII码表,也包含了几万个汉字,并支持繁体汉字以及部分日韩文字。
-
GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字。
Unicode码表
- unicode(又称统一码、万国码、单一码)是计算机科学领域里的一项业界字符编码标准。
- 容纳世界上大多数国家的所有常见文字和符号。
- 由于Unicode会先通过UTF-8,UTF-16,以及 UTF-32的编码成二进制后再存储到计算机,其中最为常见的就是UTF-8。
注意事项
- Unicode是万国码,以UTF-8编码后一个中文一般以三个字节的形式存储。
- UTF-8也要兼容ASCII编码表。
- 技术人员都应该使用UTF-8的字符集编码。
- 编码前和编码后的字符集需要一致,否则会出现中文乱码。
汉字存储和展示过程解析
7.2、字符集的编码、解码操作
String编码
方法名称 | 说明 |
---|---|
byte[] getBytes() | 使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中 |
byte[] getBytes(String charsetName) | 使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中 |
String解码
构造器 | 说明 |
---|---|
String(byte[] bytes) | 通过使用平台的默认字符集解码指定的字节数组来构造新的 String |
String(byte[] bytes, String charsetName) | 通过指定的字符集解码指定的字节数组来构造新的 String |
测试代码
import java.util.Arrays;
/**
目标:学会自己进行文字的编码和解码,为以后可能用到的场景做准备。
*/
public class Test {
public static void main(String[] args) throws Exception {
// 1、编码:把文字转换成字节(使用指定的编码)
String name = "abc我爱你中国";
// byte[] bytes = name.getBytes(); // 以当前代码默认字符集进行编码 (UTF-8)
byte[] bytes = name.getBytes("GBK"); // 指定编码
System.out.println(bytes.length);
System.out.println(Arrays.toString(bytes));
// 2、解码:把字节转换成对应的中文形式(编码前 和 编码后的字符集必须一致,否则乱码 )
// String rs = new String(bytes); // 默认的UTF-8
String rs = new String(bytes, "GBK"); // 指定GBK解码
System.out.println(rs);
}
}
8、IO 流
IO流也称为输入、输出流,就是用来读写数据的。
8.1、IO 流概述
- I表示intput,是数据从硬盘文件读入到内存的过程,称之输入,负责读。
- O表示output,是内存程序的数据从内存到写出到硬盘文件的过程,称之输出,负责写。
IO 流的分类
流的四大类
- 字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流称为字节输入流。
- 字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流称为字节输出流。
- 字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流称为字符输入流。
- 字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流称为字符输出流。
8.2、字节流的使用
8.2.1、字节输入流:每次读取一个字节
字节输入流:FileInputStream
- 作用:以内存为基准,把磁盘文件中的数据以字节的形式读取到内存中去。
构造器 | 说明 |
---|---|
public FileInputStream(File file) | 创建字节输入流管道与源文件对象接通 |
public FileInputStream(String pathname) | 创建字节输入流管道与源文件路径接通 |
方法名称 | 说明 |
---|---|
public int read() | 每次读取一个字节返回,如果字节已经没有可读的返回-1 |
public int read(byte[] buffer) | 每次读取一个字节数组返回,如果字节已经没有可读的返回-1 |
代码演示
InputStream is = new FileInputStream("D:\Code\data.txt");
int b1 = is.read();
System.out.println((char)b1);
int b2 = is.read();
System.out.println((char)b2);
//使用循环改进
int b;
while (( b = is.read() ) != -1){
System.out.print((char) b);
}
}
每次读取一个字节存在什么问题?
- 性能较慢
- 读取中文字符输出无法避免乱码问题。
8.2.2、字节输入流:每次读取一个字节数组
方法名称 | 说明 |
---|---|
public int read(byte[] buffer) | 每次读取一个字节数组返回,如果字节已经没有可读的返回-1 |
代码演示
InputStream is = new FileInputStream("D:\Code\data.txt");
byte[] buffer = new byte[3]; // 3B
int len = is.read(buffer);
System.out.println("读取了几个字节:" + len);
//可能出现后面读取的字节不足现象
String rs = new String(buffer);
System.out.println(rs);
int len2 = is.read(buffer);
System.out.println("读取了几个字节:" + len2);
//读取多少倒出多少
String rs2 = new String(buffer, 0 ,len2);
System.out.println(rs2);
//改进使用循环,每次读取一个字节数组
byte[] buffer = new byte[3];
int len; // 记录每次读取的字节数。
while ((len = is.read(buffer)) != -1) {
// 读取多少倒出多少
System.out.print(new String(buffer, 0 , len));
}
每次读取一个字节数组存在什么问题?
- 读取的性能得到了提升
- 读取中文字符输出无法避免乱码问题。
8.2.3、文件字节输入流:一次读完全部字节
如何使用字节输入流读取中文内容输出不乱码呢?
- 定义一个与文件一样大的字节数组,一次性读取完文件的全部字节。
直接把文件数据全部读取到一个字节数组可以避免乱码,是否存在问题?
- 如果文件过大,字节数组可能引起内存溢出。(理论上,实际开发并不会读取这么大的文件。)
方式一
- 自己定义一个字节数组与文件的大小一样大,然后使用读取字节数组的方法,一次性读取完成。
方法名称 | 说明 |
---|---|
public int read(byte[] buffer) | 每次读取一个字节数组返回,如果字节已经没有可读的返回-1 |
File f = new File("D:\Code\data.txt");
InputStream is = new FileInputStream(f);
//定义一个字节数组与文件的大小刚刚一样大。
byte[] buffer = new byte[(int) f.length()];
int len = is.read(buffer);
System.out.println("读取了多少个字节:" + len);
System.out.println("文件大小:" + f.length());
System.out.println(new String(buffer));
方式二
- 官方为字节输入流InputStream提供了如下API可以直接把文件的全部数据读取到一个字节数组中
方法名称 | 说明 |
---|---|
public byte[] readAllBytes() throws IOException | 直接将当前字节输入流对应的文件对象的字节数据装到一个字节数组返回 |
注意:此方法jdk9之后才支持
//读取全部字节数组
byte[] buffer = is.readAllBytes();
System.out.println(new String(buffer));
8.2.4、文件字节输出流:写字节数据到文件
字节输出流:FileOutputStream
- 作用:以内存为基准,把内存中的数据以字节的形式写出到磁盘文件中去的流。
构造器 | 说明 |
---|---|
public FileOutputStream(File file) | 创建字节输出流管道与源文件对象接通 |
public FileOutputStream(File file,boolean append) | 创建字节输出流管道与源文件对象接通,可追加数据 |
public FileOutputStream(String filepath) | 创建字节输出流管道与源文件路径接通 |
public FileOutputStream(String filepath,boolean append) | 创建字节输出流管道与源文件路径接通,可追加数据 |
字节输出流(FileOutputStream)写数据出去的API
方法名称 | 说明 |
---|---|
public void write(int a) | 写一个字节出去 |
public void write(byte[] buffer) | 写一个字节数组出去 |
public void write(byte[] buffer , int pos , int len) | 写一个字节数组的一部分出去。 |
流的关闭与刷新
方法 | 说明 |
---|---|
flush() | 刷新流,还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
代码演示
//追加数据管道
OutputStream os = new FileOutputStream("D:\Code\data.txt" , true);
//先清空之前的数据,写新数据进入
OutputStream os = new FileOutputStream("D:\Code\data.txt");
os.write('a');
os.write(98);
os.write('李'); //中文占三个字节
os.write("\r\n".getBytes()); //换行
byte[] buffer = {'a' , 97, 98, 99};
os.write(buffer);
os.write("\r\n".getBytes()); // 换行
//写数据必须,刷新数据 可以继续使用流
os.flush();
//释放资源,包含了刷新的!关闭后流不可以使用了
os.close();
8.2.5、文件拷贝
需求:
- 把某个视频复制到其他目录下的“new.avi”
思路:
- 根据数据源创建字节输入流对象
- 根据目的地创建字节输出流对象
- 读写数据,复制视频
- 释放资源
代码演示
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* 目标:学会使用字节流完成文件的复制(支持一切文件类型的复制)
*/
public class Copy {
public static void main(String[] args) {
try {
// 1、创建一个字节输入流管道与原视频接通
InputStream is = new FileInputStream("D:\Avi\old.avi");
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("D:\Avi\new.avi");
// 3、定义一个字节数组转移数据
byte[] buffer = new byte[1024];
int len; // 记录每次读取的字节数。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0 , len);
}
System.out.println("复制完成了!");
// 4、关闭流。
os.close();
is.close();
} catch (Exception e){
e.printStackTrace();
}
}
}
字节流适合做一切文件数据的拷贝吗?
任何文件的底层都是字节,拷贝是一字不漏的转移字节,只要前后文件格式、编码一致没有任何问题。
8.3、资源释放的方式
8.3.1、try-catch-finally
- finally:在异常处理时提供finally块来执行所有清除操作,比如说IO流中的释放资源
- 特点:被finally控制的语句最终一定会执行,除非JVM退出
- 异常处理标准格式:try….catch…finally
try-catch-finally 格式
try{
...
} catch (Exception e){
e.printStackTrace();
} finally{
...
}
代码演示
InputStream is = null;
OutputStream os = null;
try {
//1、创建一个字节输入流管道与原视频接通
is = new FileInputStream("D:\Avi\old.avi");
//2、创建一个字节输出流管道与目标文件接通
os = new FileOutputStream("D:\Avi\new.avi");
//3、定义一个字节数组转移数据
byte[] buffer = new byte[1024];
//记录每次读取的字节数
int len;
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0 , len);
}
System.out.println("复制完成了!");
} catch (Exception e){
e.printStackTrace();
} finally {
try {
//4、关闭流。
if(os!=null)os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(is != null) is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
注意事项
finally 代码块不要要加 retrun
public static int test(int a , int b){
try {
int c = a / b;
return c;
}catch (Exception e){
e.printStackTrace();
return -111111; // 计算出现bug.
}finally {
System.out.println("--finally--");
// 哪怕上面有return语句执行,也必须先执行完这里才可以!
// 开发中不建议在这里加return ,如果加了,返回的永远是这里的数据了,这样会出问题!
return 100;
}
}
8.3.2、try-with-resource
jdk7 改进方案
try (
// 这里面只能放置资源对象,用完会自动关闭:自动调用资源对象的close方法关闭资源(即使出现异常也会做关闭操作)
// 1、创建一个字节输入流管道与原视频接通
InputStream is = new FileInputStream("D:\Avi\old.avi");
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("D:\Avi\new.avi");
) {
// 3、定义一个字节数组转移数据
byte[] buffer = new byte[1024];
int len; // 记录每次读取的字节数。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0 , len);
}
System.out.println("复制完成了!");
} catch (Exception e){
e.printStackTrace();
}
jdk9 改进方案(了解即可)
// 这里面只能放置资源对象,用完会自动关闭:自动调用资源对象的close方法关闭资源(即使出现异常也会做关闭操作)
// 1、创建一个字节输入流管道与原视频接通
InputStream is = new FileInputStream("D:\Avi\old.avi");
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("D:\Avi\new.avi");
try ( is ; os ) {
// 3、定义一个字节数组转移数据
byte[] buffer = new byte[1024];
int len; // 记录每次读取的字节数。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0 , len);
}
System.out.println("复制完成了!");
} catch (Exception e){
e.printStackTrace();
}
注意事项
- JDK 7 以及 JDK 9的()中只能放置资源对象,否则报错
- 什么是资源呢?
- 资源都是实现了Closeable/AutoCloseable接口的类对象
public abstract class InputStream implements Closeable {}
public abstract class OutputStream implements Closeable, Flushable{}
代码演示
try (
MyConnection connection = new MyConnection(); // 最终会自动调用资源的close方法
) {
}catch (Exception e){
e.printStackTrace();
}
class MyConnection implements AutoCloseable{
@Override
public void close() throws IOException {
System.out.println("连接资源被成功释放了!");
}
}
8.4、案例:拷贝文件夹
import java.io.*;
/**
目标:拷贝文件夹
*/
public class CopyFile {
public static void main(String[] args) {
copy(new File("D:\\old") , new File("D:\\new"));
}
public static void copy(File src , File dest){
// 1、判断源目录是否存在
if(src!= null && src.exists() && src.isDirectory()){
// 2、目标目录需要创建一下 D:\new\resources
File destOne = new File(dest , src.getName());
destOne.mkdirs();
// 3、提取原目录下的全部一级文件对象
File[] files = src.listFiles();
// 4、判断是否存在一级文件对象
if(files != null && files.length > 0) {
// 5、遍历一级文件对象
for (File file : files) {
// 6、判断是文件还是文件夹,是文件直接复制过去
if(file.isFile()){
copyFile(file, new File(destOne , file.getName()));
}else {
// 7、当前遍历的是文件夹,递归复制
copy(file, destOne);
}
}
}
}
}
public static void copyFile(File srcFile, File destFile){
try (
// 1、创建一个字节输入流管道与原视频接通
InputStream is = new FileInputStream(srcFile);
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream(destFile);
) {
// 3、定义一个字节数组转移数据
byte[] buffer = new byte[1024];
int len; // 记录每次读取的字节数。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0 , len);
}
} catch (Exception e){
e.printStackTrace();
}
}
}
8.5、字符流的使用
8.5.1、字符输入流-一次读取一个字符
字节流读取中文输出会存在什么问题?
会乱码。或者内存溢出。
读取中文输出,哪个流更合适,为什么?
字符流更合适,最小单位是按照单个字符读取的。
文件字符输入流:Reader
- 作用:以内存为基准,把磁盘文件中的数据以字符的形式读取到内存中去。
构造器 | 说明 |
---|---|
public FileReader(File file) | 创建字符输入流管道与源文件对象接通 |
public FileReader(String pathname) | 创建字符输入流管道与源文件路径接通 |
方法名称 | 说明 |
---|---|
public int read() | 每次读取一个字符返回,如果字符已经没有可读的返回-1 |
public int read(char[] buffer) | 每次读取一个字符数组,返回读取的字符个数,如果字符已经没有可读的返回-1 |
代码演示
Reader fr = new FileReader("D:\Code\data.txt");
// 读取一个字符返回,没有可读的字符了返回-1
int code = fr.read();
System.out.print((char)code);
int code1 = fr.read();
System.out.print((char)code1);
// 使用循环读取字符
int code;
while ((code = fr.read()) != -1){
System.out.print((char) code);
}
字符流的好处
- 读取中文字符不会出现乱码(如果代码文件编码一致)
每次读取一个字符存在什么问题?
- 性能较慢
8.5.2、文件字符输入流-一次读取一个字符数组
方法名称 | 说明 |
---|---|
public int read(char[] buffer) | 每次读取一个字符数组,返回读取的字符个数,如果字符已经没有可读的返回-1 |
代码演示
// 1、创建一个文件字符输入流与源文件接通
Reader fr = new FileReader("D:\Code\data.txt");
// 2、用循环,每次读取一个字符数组的数据。 1024 + 1024 + 8
char[] buffer = new char[1024]; // 1K字符
int len;
while ((len = fr.read(buffer)) != -1) {
String rs = new String(buffer, 0, len);
System.out.print(rs);
}
每次读取一个字符数组的优势?
- 读取的性能得到了提升。
- 读取中文字符输出不会乱码。
8.5.3、文件字符输出流
文件字符输出流:FileWriter
- 作用:以内存为基准,把内存中的数据以字符的形式写出到磁盘文件中去的流。
构造器 | 说明 |
---|---|
public FileWriter(File file) | 创建字符输出流管道与源文件对象接通 |
public FileWriter(File file,boolean append) | 创建字符输出流管道与源文件对象接通,可追加数据 |
public FileWriter(String filepath) | 创建字符输出流管道与源文件路径接通 |
public FileWriter(String filepath,boolean append) | 创建字符输出流管道与源文件路径接通,可追加数据 |
文件字符输出流(FileWriter)写数据出去的API
方法名称 | 说明 |
---|---|
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) | 写一个字符串的一部分 |
void write(int c) | 写一个字符 |
流的关闭与刷新
方法 | 说明 |
---|---|
flush() | 刷新流,还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
代码演示
// 覆盖管道,每次启动都会清空文件之前的数据
Writer fw = new FileWriter("D:\Code\data.txt");
// 追加数据管道,每次启动不会清空文件之前的数据
Writer fw = new FileWriter("D:\Code\data.txt", true);
fw.write(98);
fw.write('a');
fw.write('徐');
fw.write("\r\n"); // 换行
fw.write("abc我是中国人");
fw.write("\r\n"); // 换行
char[] chars = "abc我是中国人".toCharArray();
fw.write(chars);
fw.write("\r\n"); // 换行
8.6、缓冲流
8.6.1、缓冲流概述
8.6.2、字节缓冲流
- 字节缓冲输入流:BufferedInputStream,提高字节输入流读取数据的性能,读写功能上并无变化。
- 字节缓冲输出流:BufferedOutputStream,提高字节输出流读取数据的性能,读写功能上并无变化。
字节缓冲流性能优化原理
- 字节缓冲输入流自带了8KB缓冲池,以后我们直接从缓冲池读取数据,所以性能较好。
- 字节缓冲输出流自带了8KB缓冲池,数据就直接写入到缓冲池中去,写数据性能极高了。
构造器
构造器 | 说明 |
---|---|
public BufferedInputStream(InputStream is) | 可以把低级的字节输入流包装成一个高级的缓冲字节输入流管道,从而提高字节输入流读数据的性能 |
public BufferedOutputStream(OutputStream os) | 可以把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能 |
代码演示
// 创建一个字节输入流管道
InputStream is = new FileInputStream("D:\Avi\old.avi");
// 把原始的字节输入流包装成高级的缓冲字节输入流
InputStream bis = new BufferedInputStream(is);
// 创建一个字节输出流管道与
OutputStream os = new FileOutputStream("D:\Avi\new.avi");
// 把字节输出流管道包装成高级的缓冲字节输出流管道
OutputStream bos = new BufferedOutputStream(os);
8.6.3、字节缓冲流的性能分析
分别使用不同的方式复制大视频观察性能情况
需求:
- 分别使用低级字节流和高级字节缓冲流拷贝大视频,记录耗时。
分析:
- 使用低级的字节流按照一个一个字节的形式复制文件。
- 使用低级的字节流按照一个一个字节数组的形式复制文件。
- 使用高级的缓冲字节流按照一个一个字节的形式复制文件。
- 使用高级的缓冲字节流按照一个一个字节数组的形式复制文件。
代码演示
import java.io.*;
/**
目标:利用字节流的复制统计各种写法形式下缓冲流的性能执行情况。
复制流:
(1)使用低级的字节流按照一个一个字节的形式复制文件。
(2)使用低级的字节流按照一个一个字节数组的形式复制文件。
(3)使用高级的缓冲字节流按照一个一个字节的形式复制文件。
(4)使用高级的缓冲字节流按照一个一个字节数组的形式复制文件。
源文件:C:\course\3-视频\18、IO流-文件字节输出流FileOutputStream写字节数据出去.avi
目标文件:C:\course\
小结:
使用高级的缓冲字节流按照一个一个字节数组的形式复制文件,性能好,建议开发使用!
*/
public class ByteBufferTimeDemo {
private static final String SRC_FILE = "D:\\course\\基础加强\\day08-日志框架、阶段项目\\视频\\14、用户购票功能.avi";
private static final String DEST_FILE = "D:\\course\\";
public static void main(String[] args) {
// copy01(); // 使用低级的字节流按照一个一个字节的形式复制文件:慢的让人简直无法忍受。直接被淘汰。
copy02(); // 使用低级的字节流按照一个一个字节数组的形式复制文件: 比较慢,但是还是可以忍受的! 6.29s
// copy03(); // 缓冲流一个一个字节复制:很慢,不建议使用。 37.071s
copy04(); // 缓冲流一个一个字节数组复制:飞快,简直太完美了(推荐使用) 1.399s
}
private static void copy04() {
long startTime = System.currentTimeMillis();
try (
// 1、创建低级的字节输入流与源文件接通
InputStream is = new FileInputStream(SRC_FILE);
// a.把原始的字节输入流包装成高级的缓冲字节输入流
InputStream bis = new BufferedInputStream(is);
// 2、创建低级的字节输出流与目标文件接通
OutputStream os = new FileOutputStream(DEST_FILE + "video4.avi");
// b.把字节输出流管道包装成高级的缓冲字节输出流管道
OutputStream bos = new BufferedOutputStream(os);
) {
// 3、定义一个字节数组转移数据
byte[] buffer = new byte[1024];
int len; // 记录每次读取的字节数。
while ((len = bis.read(buffer)) != -1){
bos.write(buffer, 0 , len);
}
} catch (Exception e){
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("使用缓冲的字节流按照一个一个字节数组的形式复制文件耗时:" + (endTime - startTime)/1000.0 + "s");
}
private static void copy03() {
long startTime = System.currentTimeMillis();
try (
// 1、创建低级的字节输入流与源文件接通
InputStream is = new FileInputStream(SRC_FILE);
// a.把原始的字节输入流包装成高级的缓冲字节输入流
InputStream bis = new BufferedInputStream(is);
// 2、创建低级的字节输出流与目标文件接通
OutputStream os = new FileOutputStream(DEST_FILE + "video3.avi");
// b.把字节输出流管道包装成高级的缓冲字节输出流管道
OutputStream bos = new BufferedOutputStream(os);
){
// 3、定义一个变量记录每次读取的字节(一个一个字节的复制)
int b;
while ((b = bis.read()) != -1){
bos.write(b);
}
}catch (Exception e){
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("使用缓冲的字节流按照一个一个字节的形式复制文件耗时:" + (endTime - startTime)/1000.0 + "s");
}
private static void copy02() {
long startTime = System.currentTimeMillis();
try (
// 这里面只能放置资源对象,用完会自动关闭:自动调用资源对象的close方法关闭资源(即使出现异常也会做关闭操作)
// 1、创建一个字节输入流管道与原视频接通
InputStream is = new FileInputStream(SRC_FILE);
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream(DEST_FILE + "video2.avi")
) {
// 3、定义一个字节数组转移数据
byte[] buffer = new byte[1024];
int len; // 记录每次读取的字节数。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0 , len);
}
} catch (Exception e){
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("使用低级的字节流按照一个一个字节数组的形式复制文件耗时:" + (endTime - startTime)/1000.0 + "s");
}
/**
使用低级的字节流按照一个一个字节的形式复制文件
*/
private static void copy01() {
long startTime = System.currentTimeMillis();
try (
// 1、创建低级的字节输入流与源文件接通
InputStream is = new FileInputStream(SRC_FILE);
// 2、创建低级的字节输出流与目标文件接通
OutputStream os = new FileOutputStream(DEST_FILE + "video1.avi")
){
// 3、定义一个变量记录每次读取的字节(一个一个字节的复制)
int b;
while ((b = is.read()) != -1){
os.write(b);
}
}catch (Exception e){
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("使用低级的字节流按照一个一个字节的形式复制文件耗时:" + (endTime - startTime)/1000.0 + "s");
}
}
总结
- 注意如果改变 copy02() 中的 byte[] buffer = new byte[1024*8],可以做到和 copy04() 的速度差不多,原因是 copy04() 中缓冲池大小默认为 1024*8
- 但是如果改变 copy04() 中的 byte[] buffer = new byte[1024*8],速度反而会变慢,所以使用缓冲流的时候,数组不宜过大,建议使用byte[] buffer = new byte[1024]
- 建议使用字节缓冲输入流、字节缓冲输出流,结合字节数组的方式,目前来看是性能最优的组合。
8.6.4、字符缓冲流
字符缓冲流自带8K缓冲区
字符缓冲输入流
- 字符缓冲输出流:BufferedWriter。
- 作用:提高字符输出流写取数据的性能,除此之外多了换行功能
构造器 | 说明 |
---|---|
public BufferedWriter(Writer w) | 可以把低级的字符输出流包装成一个高级的缓冲字符输出流管道,从而提高字符输出流写数据的性能 |
字符缓冲输出流新增功能
方法 | 说明 |
---|---|
public void newLine() | 换行操作 |
字符缓冲输出流
- 字符缓冲输出流:BufferedWriter。
- 作用:提高字符输出流写取数据的性能,除此之外多了换行功能
构造器 | 说明 |
---|---|
public BufferedWriter(Writer w) | 可以把低级的字符输出流包装成一个高级的缓冲字符输出流管道,从而提高字符输出流写数据的性能 |
字符缓冲输出流新增功能
方法 | 说明 |
---|---|
public void newLine() | 换行操作 |
// 创建一个字符输入流
Reader fr = new FileReader("D:\Code\data.txt");
// 把低级的字符输入流包装成高级的缓冲字符输入流。
BufferedReader br = new BufferedReader(fr);
// 创建一个字符输出流
Writer fw = new FileWriter("D:\Code\out.txt"); // 覆盖管道,每次启动都会清空文件之前的数据
Writer fw = new FileWriter("D:\Code\out.txt", true); // 追加数据
// 把低级的字符输出流包装成高级的缓冲字符输出流。
BufferedWriter bw = new BufferedWriter(fw);
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
bw.write("两个黄鹂鸣翠柳");
bw.newLine(); // 换行
bw.write("一行白鹭上青天")
注意事项
- 要追加数据的话记得是往FileWriter里面添加true
- 而不是往高级管道BufferedWriter里面添加
Writer fw = new FileWriter("D:\Code\out.txt", true);
8.6.5、字符缓存流案例
拷贝出师表
需求:
把《出师表》的文章顺序进行恢复到一个新文件中。
分析:
- 定义一个缓存字符输入流管道与源文件接通。
- 定义一个List集合存储读取的每行数据。
- 定义一个循环按照行读取数据,存入到List集合中去。
- 对List集合中的每行数据按照首字符编号升序排序。
- 定义一个缓存字符输出管道与目标文件接通。
- 遍历List集合中的每个元素,用缓冲输出管道写出并换行。
代码演示
import java.io.*;
import java.util.*;
/**
目标:完成出师表顺序的恢复,并存入到另一个新文件中去。
*/
public class BufferedCharTest3 {
public static void main(String[] args) {
try(
// 1、创建缓冲字符输入流管道与源文件接通
BufferedReader br = new BufferedReader(new FileReader("D:\\重装系统前\\、代码笔记\\黑马程序员\\Java基础\\配套笔记\\day20、IO流二\\代码\\io-app2\\src\\csb.txt"));
// 5、定义缓冲字符输出管道与目标文件接通
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\重装系统前\\、代码笔记\\黑马程序员\\Java基础\\配套笔记\\day20、IO流二\\代码\\io-app2\\src\\new.txt"));
) {
// 2、定义一个List集合存储每行内容
List<String> data = new ArrayList<>();
// 3、定义循环,按照行读取文章
String line;
while ((line = br.readLine()) != null){
data.add(line);
}
System.out.println(data);
// 4、排序
// 自定义排序规则
List<String> sizes = new ArrayList<>();
Collections.addAll(sizes, "一","二","三","四","五","陆","柒","八","九","十","十一");
Collections.sort(data, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
// o1 八......
// o2 柒......
return sizes.indexOf(o1.substring(0, o1.indexOf(".")))
- sizes.indexOf(o2.substring(0, o2.indexOf(".")));
}
});
System.out.println(data);
// 6、遍历集合中的每行文章写出去,且要换行
for (String datum : data) {
bw.write(datum);
bw.newLine(); // 换行
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
8.7、转换流
问题引出
1、之前我们使用字符流读取中文是否有乱码?
- 没有的,因为代码编码和文件编码都是UTF-8。
2、如果代码编码和文件编码不一致,使用字符流直接读取还能不乱码吗?
- 会乱码。
- 文件编码和读取的编码必须一致才不会乱码。
8.7.1、字符输入转换流
- 可以提取文件(GBK)的原始字节流,原始字节不会存在问题。
- 然后把字节流以指定编码转换成字符输入流,这样字符输入流中的字符就不乱码了
构造器
字符输入转换流:InputStreamReader,可以把原始的字节流按照指定编码转换成字符输入流。
构造器 | 说明 |
---|---|
public InputStreamReader(InputStream is) | 可以把原始的字节流按照代码默认编码转换成字符输入流。几乎不用,与默认的FileReader一样。 |
public InputStreamReader(InputStream is ,String charset) | 可以把原始的字节流按照指定编码转换成字符输入流,这样字符流中的字符就不乱码了(重点) |
代码演示
InputStream is = new FileInputStream("D:\\Code\\java-basics\\file\\test-ansi.txt");
Reader isr = new InputStreamReader(is , "GBK");
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
8.7.2、字符输出转换流
问题引入
1、如果需要控制写出去的字符使用的编码,怎么办?
- 可以把字符以指定编码获取字节后再使用字节输出流写出去:“我爱你中国”.getBytes(编码)
- 也可以使用字符输出转换流实现。
构造器
字符输入转换流:OutputStreamWriter,可以把字节输出流按照指定编码转换成字符输出流。
构造器 | 说明 |
---|---|
public OutputStreamWriter(OutputStream os) | 可以把原始的字节输出流按照代码默认编码转换成字符输出流。几乎不用。 |
public OutputStreamWriter(OutputStream os,String charset) | 可以把原始的字节输出流按照指定编码转换成字符输出流(重点) |
代码演示
OutputStream os = new FileOutputStream("D:\\Code\\java-basics\\file\\test-ansi.txt",true);
Writer osw = new OutputStreamWriter(os , "GBK");
BufferedWriter bw = new BufferedWriter(osw);
bw.newLine();
bw.write("苹果雪梨水蜜桃");
bw.close();
8.8、序列化对象
8.8.1、对象序列化
- 作用:以内存为基准,把内存中的对象存储到磁盘文件中去,称为对象序列化。
- 使用到的流是对象字节输出流:ObjectOutputStream
对象字节输出流
- 构造器
构造器 | 说明 |
---|---|
public ObjectOutputStream(OutputStream out) | 把低级字节输出流包装成高级的对象字节输出流 |
- 方法
方法名称 | 说明 |
---|---|
public final void writeObject(Object obj) | 把对象写出去到对象序列化流的文件中去 |
学生对象
import java.io.Serializable;
/**
对象如果要序列化,必须实现Serializable序列化接口。
*/
public class Student implements Serializable {
// 申明序列化的版本号码
// 序列化的版本号与反序列化的版本号必须一致才不会出错!
private static final long serialVersionUID = 1;
private String name;
private String loginName;
// transient修饰的成员变量不参与序列化了
private transient String passWord;
private int age ;
public Student(){
}
public Student(String name, String loginName, String passWord, int age) {
this.name = name;
this.loginName = loginName;
this.passWord = passWord;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", loginName='" + loginName + '\'' +
", passWord='" + passWord + '\'' +
", age=" + age +
'}';
}
}
注意事项
- 对象必须实现序列化接口
- 如果更新了数据,可以修改版本号,这样如果调用的是之前版本号的数据会报错。
// 对象必须实现序列化接口
public class Student implements Serializable {}
// transient修饰的成员变量不参与序列化了
private transient String passWord;
// 序列化的版本号与反序列化的版本号必须一致才不会出错!
private static final long serialVersionUID = 1;
测试代码
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
/**
目标:学会对象序列化,使用 ObjectOutputStream 把内存中的对象存入到磁盘文件中。
transient修饰的成员变量不参与序列化了
对象如果要序列化,必须实现Serializable序列化接口。
申明序列化的版本号码
序列化的版本号与反序列化的版本号必须一致才不会出错!
private static final long serialVersionUID = 1;
*/
public class ObjectOutputStreamDemo1 {
public static void main(String[] args) throws Exception {
// 1、创建学生对象
Student s = new Student("陈磊", "chenlei","1314520", 21);
// 2、对象序列化:使用对象字节输出流包装字节输出流管道
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("io-app2/src/obj.txt"));
// 3、直接调用序列化方法
oos.writeObject(s);
// 4、释放资源
oos.close();
System.out.println("序列化完成了~~");
}
}
8.8.2、对象反序列化
- 使用到的流是对象字节输入流:ObjectInputStream
- 作用:以内存为基准,把存储到磁盘文件中去的对象数据恢复成内存中的对象,称为对象反序列化。
对象字节输入流
- 构造器
构造器 | 说明 |
---|---|
public ObjectInputStream(InputStream out) | 把低级字节输如流包装成高级的对象字节输入流 |
- 方法
方法名称 | 说明 |
---|---|
public Object readObject() | 把存储到磁盘文件中去的对象数据恢复成内存中的对象返回 |
代码演示
import java.io.FileInputStream;
import java.io.ObjectInputStream;
/**
目标:学会进行对象反序列化:使用对象字节输入流把文件中的对象数据恢复成内存中的Java对象。
*/
public class ObjectInputStreamDemo2 {
public static void main(String[] args) throws Exception {
// 1、创建对象字节输入流管道包装低级的字节输入流管道
ObjectInputStream is = new ObjectInputStream(new FileInputStream("io-app2/src/obj.txt"));
// 2、调用对象字节输入流的反序列化方法
Student s = (Student) is.readObject();
System.out.println(s);
}
}
8.9、打印流
- 作用:打印流可以实现方便、高效的打印数据到文件中去。打印流一般是指:PrintStream,PrintWriter两个类。
- 可以实现打印什么数据就是什么数据,例如打印整数97写出去就是97,打印boolean的true,写出去就是true。
PrintStream
- 构造器
构造器 | 说明 |
---|---|
public PrintStream(OutputStream os) | 打印流直接通向字节输出流管道 |
public PrintStream(File f) | 打印流直接通向文件对象 |
public PrintStream(String filepath) | 打印流直接通向文件路径 |
- 方法
方法 | 说明 |
---|---|
public void print(Xxx xx) | 打印任意类型的数据出去 |
PrintWriter
- 构造器
构造器 | 说明 |
---|---|
public PrintWriter(OutputStream os) | 打印流直接通向字节输出流管道 |
public PrintWriter (Writer w) | 打印流直接通向字符输出流管道 |
public PrintWriter (File f) | 打印流直接通向文件对象 |
public PrintWriter (String filepath) | 打印流直接通向文件路径 |
- 方法
方法 | 说明 |
---|---|
public void print(Xxx xx) | 打印任意类型的数据出去 |
PrintStream和PrintWriter的区别
- 打印数据功能上是一模一样的,都是使用方便,性能高效(核心优势)
- PrintStream继承自字节输出流OutputStream,支持写字节数据的方法。
- PrintWriter继承自字符输出流Writer,支持写字符数据出去。
代码演示
import java.io.PrintWriter;
/**
目标:学会使用打印流 高效 方便写数据到文件。
*/
public class PrintDemo1 {
public static void main(String[] args) throws Exception {
// 1、创建一个打印流对象
// PrintStream ps = new PrintStream(new FileOutputStream("io-app2/src/ps.txt"));
// PrintStream ps = new PrintStream(new FileOutputStream("io-app2/src/ps.txt" , true)); // 追加数据,在低级管道后面加True
// PrintStream ps = new PrintStream("io-app2/src/ps.txt" );
PrintWriter ps = new PrintWriter("io-app2/src/ps.txt"); // 打印功能上与PrintStream的使用没有区别
ps.println(97);
ps.println('a');
ps.println(23.3);
ps.println(true);
ps.println("我是打印流输出的,我是啥就打印啥");
ps.close();
}
}
输出语句的重定向
属于打印流的一种应用,可以把输出语句的打印位置改到文件。
import java.io.PrintStream;
/**
目标:了解改变输出语句的位置到文件
*/
public class PrintDemo2 {
public static void main(String[] args) throws Exception {
System.out.println("锦瑟无端五十弦");
System.out.println("一弦一柱思华年");
// 改变输出语句的位置(重定向)
PrintStream ps = new PrintStream("io-app2/src/log.txt");
System.setOut(ps); // 把系统打印流改成我们自己的打印流
System.out.println("庄生晓梦迷蝴蝶");
System.out.println("望帝春心托杜鹃");
}
}
8.10、Properties
Properties 属性集对象
其实就是一个Map集合,但是我们一般不会当集合使用,因为HashMap更好用。
Properties核心作用
- Properties代表的是一个属性文件,可以把自己对象中的键值对信息存入到一个属性文件中去。
- 属性文件:后缀是.properties结尾的文件,里面的内容都是 key=value,后续做系统配置信息的。
Properties 的 API
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 Object setProperty(String key, String value) | 保存键值对(put) |
public String getProperty(String key) | 使用此属性列表中指定的键搜索属性值 (get) |
public Set stringPropertyNames() | 所有键的名称的集合 (keySet()) |
存储对象
import java.io.FileWriter;
import java.util.Properties;
/**
目标:Properties的概述和使用(框架底层使用,了解这个技术即可)(保存数据到属性文件)
Properties: 属性集对象。
其实就是一个Map集合。也就是一个键值对集合,但是我们一般不会当集合使用,
因为有HashMap。
Properties核心作用:
Properties代表的是一个属性文件,可以把键值对的数据存入到一个属性文件中去。
属性文件:后缀是.properties结尾的文件,里面的内容都是 key=value。
大家在后期学的很多大型框架技术中,属性文件都是很重要的系统配置文件。
users.properties
admin=123456
dlei=dlei
需求:使用Properties对象生成一个属性文件,里面存入用户名和密码信息。
Properties的方法:
-- public Object setProperty(String key, String value) : 保存一对属性。 (put)
-- public String getProperty(String key) : 使用此属性列表中指定的键搜索属性值 (get)
-- public Set<String> stringPropertyNames() : 所有键的名称的集合 (keySet())
-- public void store(OutputStream out, String comments): 保存数据到属性文件中去
-- public void store(Writer fw, String comments): 保存数据到属性文件中去
小结:
Properties可以保存键值对数据到属性文件
*/
public class PropertiesDemo01 {
public static void main(String[] args) throws Exception {
// 需求:使用Properties把键值对信息存入到属性文件中去。
Properties properties = new Properties();
properties.setProperty("admin", "123456");
properties.setProperty("dlei", "003197");
properties.setProperty("heima", "itcast");
System.out.println(properties);
/**
参数一:保存管道 字符输出流管道
参数二:保存心得
*/
properties.store(new FileWriter("io-app2/src/users.properties")
, "this is users!! i am very happy! give me 100!");
}
}
保存的users.properties文件
#this is users!! i am very happy! give me 100!
#Sat Aug 14 16:21:04 CST 2021
dlei=003197
admin=123456
heima=itcast
获取对象
import java.io.FileReader;
import java.util.Properties;
/**
目标:Properties读取属性文件中的键值对信息。(读取)
Properties的方法:
-- public Object setProperty(String key, String value) : 保存一对属性。
-- public String getProperty(String key) :使用此属性列表中指定的键搜索属性值
-- public Set<String> stringPropertyNames() :所有键的名称的集合
-- public void store(OutputStream out, String comments):保存数据到属性文件中去
-- public synchronized void load(InputStream inStream):加载属性文件的数据到属性集对象中去
-- public synchronized void load(Reader fr):加载属性文件的数据到属性集对象中去
小结:
属性集对象可以加载读取属性文件中的数据!!
*/
public class PropertiesDemo02 {
public static void main(String[] args) throws Exception {
// 需求:Properties读取属性文件中的键值对信息。(读取)
Properties properties = new Properties();
System.out.println(properties);
// 加载属性文件中的键值对数据到属性对象properties中去
properties.load(new FileReader("io-app2/src/users.properties"));
System.out.println(properties);
String rs = properties.getProperty("dlei");
System.out.println(rs);
String rs1 = properties.getProperty("admin");
System.out.println(rs1);
}
}
输出结果
{}
{dlei=003197, admin=123456, heima=itcast}
003197
123456
8.11、IO 框架
commons-io 概述
- commons-io是apache开源基金组织提供的一组有关IO操作的类库,可以提高IO功能开发的效率。
- commons-io工具包提供了很多有关io操作的类。有两个主要的类FileUtils, IOUtils
FileUtils 主要方法
方法名 | 说明 |
---|---|
String readFileToString(File file, String encoding) | 读取文件中的数据, 返回字符串 |
void copyFile(File srcFile, File destFile) | 复制文件。 |
void copyDirectoryToDirectory(File srcDir, File destDir) | 复制文件夹。 |
使用步骤
- 在项目中创建一个文件夹:lib
- 将commons-io-2.6.jar文件复制到lib文件夹
- 在jar文件上点右键,选择 Add as Library -> 点击OK
- 在类中导包使用
代码演示
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
/**
目标:Commons-io包的使用介绍。
什么是Commons-io包?
commons-io是apache开源基金组织提供的一组有关IO操作的类库,
可以挺提高IO功能开发的效率。commons-io工具包提供了很多有关io操作的类,
见下表:
| 包 | 功能描述 |
| ----------------------------------- | :------------------------------------------- |
| org.apache.commons.io | 有关Streams、Readers、Writers、Files的工具类 |
| org.apache.commons.io.input | 输入流相关的实现类,包含Reader和InputStream |
| org.apache.commons.io.output | 输出流相关的实现类,包含Writer和OutputStream |
| org.apache.commons.io.serialization | 序列化相关的类
步骤:
1. 下载commons-io相关jar包;http://commons.apache.org/proper/commons-io/
2. 把commons-io-2.6.jar包复制到指定的Module的lib目录中
3. 将commons-io-2.6.jar加入到classpath中
小结:
IOUtils和FileUtils可以方便的复制文件和文件夹!!
*/
public class CommonsIODemo01 {
public static void main(String[] args) throws Exception {
// 1.完成文件复制!
// IOUtils.copy(new FileInputStream("D:\\resources\\hushui.jpeg"),
// new FileOutputStream("D:\\resources\\hushui2.jpeg"));
// 2.完成文件复制到某个文件夹下!
// FileUtils.copyFileToDirectory(new File("D:\\resources\\hushui.jpeg"), new File("D:/"));
// 3.完成文件夹复制到某个文件夹下!
// FileUtils.copyDirectoryToDirectory(new File("D:\\resources") , new File("D:\\new"));
// FileUtils.deleteDirectory(new File("D:\\new"));
// JDK1.7 自己也做了一些一行代码完成复制的操作:New IO的技术
// Files.copy(Path.of("D:\\resources\\hushui.jpeg"), Path.of("D:\\resources\\hushui3.jpeg"));
FileUtils.deleteDirectory(new File("D:\\new"));
}
}
本文章参考B站 Java入门基础视频教程,java零基础自学首选黑马程序员Java入门教程(含Java项目和Java真题),仅供个人学习使用,部分内容为本人自己见解,与黑马程序员无关。