异常(Exception)
异常介绍(Java语言中,将程序执行中发生的不正常情况称为“异常”
(开发过程中的语法错误和逻辑错误不是异常
))
Error(错误)(Java虚拟机无法解决的严重问题)
Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError[栈溢出]和OOM(out ofmemory),Error 是严重错误,程序会崩溃。
Exception(可以使用针对性的代码进行处理)
其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等,Exception 分为两大类:运行时异常[程序运行时,发生的异常]和编译时异常[编程时,编译器检查出的异常]。
运行时异常(程序运行时,发生的异常)
- NullPointerException空指针异常
- ArithmeticException数学运算异常
- ArrayIndexOutOfBoundsException数组下标越界异常
- ClassCastException类型转换异常
- NumberFormatException数字格式不正确异常
编译异常(编译器检查出的异常)
编译异常是指在编译期间,就必须处理的异常,否则代码不能通过编译。
SQLException //操作数据库时,查询表可能发生异常IOException //操作文件时,发生的异常
EOFException // 操作文件,到文件末尾,发生异常
FileNotFoundException //当操作一个不存在的文件时,发生异常
ClassNotFoundException //加载类,而该类不存在时,异常
llegalArguementException //参数异常
异常处理
try-catch-finally(自行处理)(系统将异常封装成Exception 对象e,传递给catch)
程序员在代码中捕获发生的异常,自行处理
- 可以有多个catch语句,捕获不同的异常(进行不同的业务处理),要求
父类异常在后,子类异常在前
,比如(Exception 在后,NullPointerException 在前),如果发生异常,只会匹配一个catch - 可以进行 try-finally 配合使用,这种用法相当于没有捕获异常,因此程序会直接崩掉/退出。应用场景,就是执行一段代码,
不管是否发生异常,都必须执行某个业务逻辑
finally代码块一定会执行(会覆盖catch里面的返回值)
public class Exception01 {
public static int method() {
try {
String[] names = new String[3]; // 修正了数组定义
if (names[1] != null && names[1].equals("tom")) { // NullPointerException
System.out.println(names[1]);
} else {
names[3] = "hspedu"; // ArrayIndexOutOfBoundsException
}
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
return 2; // 捕获数组越界异常
} catch (NullPointerException e) {
return 3; // 捕获空指针异常
} finally {
return 4; // finally块会覆盖try/catch中的返回值
}
}
public static void main(String[] args) {
System.out.println(method()); // 输出4
}
}
throws(交给调用者(方法)来处理,最顶级的处理者就是JVM)(在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类
)(程序中如果没有处理,则默认的异常处理方式是throws)(子类重写父类的方法时,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型
)
将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM
自定义异常(当程序中出现了某些“错误”,但该错误信息并没有在Throwable子类中描述处理
,这个时候可以自己设计异常类,用于描述该错误信息)
- 定义类:自定义异常类名(程序员自己写)继承Exception或RuntimeException
- 如果继承Exception,属于编译异常
- 如果继承RuntimeException,属于运行异常(
一般来说,继承RuntimeException
)
public class CustomException {
public static void main(String[] args) {
int age = 180;
// 要求范围在 18 – 120 之间,否则抛出一个自定义异常
if (!(age >= 18 && age <= 120)) {
// 通过构造器设置信息,抛出自定义异常
throw new AgeException("年龄需要在 18~120 之间");
}
System.out.println("你的年龄范围正确.");
}
}
// 自定义一个异常
// 1. 自定义异常通常继承 RuntimeException
// 2. 这样自定义异常成为运行时异常,可以使用默认的处理机制
class AgeException extends RuntimeException {
// 构造器接收一个错误信息,并传递给父类
public AgeException(String message) {
super(message);
}
}
throws(异常处理的一种方式,在方法声明处
,后面跟异常类型
)和throw(手动生成异常对象的关键字,在方法体中
,后面跟异常对象
) 的区别
练习题
try catch和finally的执行顺序
泛(广泛)型(类型)(在给泛型指定具体类型后,可以传入该类型或者其子类类型
)(在实际开发中,我们往往简写(编译器会进行类型推断),ArrayList<Integer>
list3 = new ArrayList<>
();)(传入的必须是引用类型
,默认是Object类型)(泛型可以有多个)
泛型的理解和好处
看一个需求
1)请编写程序,在ArrayList 中,添加3个Dog对象
2)Dog对象含有name 和 age,并输出name 和 age(要求使用getXxx())
使用传统方法(不能对加入到集合 ArrayList中的数据类型进行约束(不安全))(影响效率)
- 不能对加入到集合 ArrayList中的数据类型进行约束(不安全)
- 遍历的时候,需要进行类型转换,如果集合中的数据量较大,对效率有影响
this.age = age;
}
public String getName() { return name;
}
public void setName(String name) { this.name = name;
}
public int getAge() { return age;
}
public void setAge(int age) { this.age = age;
}
}
使用泛型(使用泛型 ArrayList,类型不匹配时编译器会报错,提高了安全性)
- 编译时,检查添加元素的类型,提高了安全性
- 减少了类型转换的次数,提高效率[说明]
- 不使用泛型Dog -加入->0bject -取出->Dog //放入到ArrayList 会先转成 Object,在取出时,还需要转换成Dog
- 使用泛型
Dog -> Dog -> Dog // 放入时,和取出时,不需要类型转换,提高效率
- 不再提示编译警告
import java.util.ArrayList;
@SuppressWarnings({"all"})
public class Generic02 {
public static void main(String[] args) {
// 使用泛型来解决类型安全问题
// 1. ArrayList<Dog> 表示集合中的元素是 Dog 类型
// 2. 添加不符合类型的元素会在编译时报错
// 3. 遍历时可以直接取出 Dog 类型,而不是 Object
ArrayList<Dog> arrayList = new ArrayList<>(); // 使用泛型 ArrayList<Dog>
arrayList.add(new Dog("旺财", 10));
arrayList.add(new Dog("发财", 1));
arrayList.add(new Dog("小黄", 5));
// 编译器会报错,因为类型不匹配
// arrayList.add(new Cat("招财猫", 8));
// 输出每只狗的名字和年龄
System.out.println("===使用泛型====");
for (Dog dog : arrayList) {
System.out.println(dog.getName() + " - " + dog.getAge());
}
}
}
/**
* Dog 类
*/
class Dog {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
/**
* Cat 类
*/
class Cat {
private String name;
private int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
泛型的语法
泛型的声明
interface 接口{} 和 class 类<K,V>{}
//比如:List , ArrayList
说明:
- 其中,T,K,V不代表值,而是表示类型。
- 任意字母都可以。常用T表示,是Type的缩写
泛型的实例化
要在类名后面指定类型参数的值(类型)。
如:
-
List<String>
strList = new ArrayList();[举例说明] -
Iterator<Customer>
iterator = customers.iterator();
泛型的使用
- interface List{},public class HashSet{}… 等等说明:
T,E只能是引用类型
看看下面语句是否正确?:
List<Integer> list = new ArrayList<Integer>();
//OKList<int> list2 = new ArrayList<int>();//错误
- 在给泛型指定具体类型后,
可以传入该类型或者其子类类型
- 泛型使用形式(在实际开发中,我们往往简写,/编译器会进行类型推断, 推荐使用下面写法)
List list1 = new ArrayList();
List list2 = new ArrayList<>();[说明:] - 如果我们这样写 List list3 = new ArrayList();默认给它的 泛型是[E就是 Object]
import java.util.ArrayList;
import java.util.List;
/**
* @创建人 wdl
* @创建时间 2024/9/14
* @描述
*/
@SuppressWarnings({"all"})
public class GenericDetail {
public static void main(String[] args) {
// 1. 给泛型指向数据类型时,要求是引用类型,不能是基本数据类型
List<Integer> list = new ArrayList<Integer>(); // OK
// List<int> list2 = new ArrayList<int>(); // 错误,基本类型不允许
// 2. 说明
// 因为 E 指定了 A 类型, 构造器传入了 new A()
// 在给泛型指定具体类型后,可以传入该类型或者其子类类型
Pig<A> aPig = new Pig<A>(new A());
aPig.f();
Pig<A> aPig2 = new Pig<A>(new B()); // B 是 A 的子类
aPig2.f();
// 3. 泛型的使用形式
ArrayList<Integer> list1 = new ArrayList<Integer>();
List<Integer> list2 = new ArrayList<Integer>();
// 在实际开发中,我们往往简写,编译器会进行类型推断
ArrayList<Integer> list3 = new ArrayList<>();
List<Integer> list4 = new ArrayList<>();
ArrayList<Pig> pigs = new ArrayList<>(); // 这里 Pig 没有泛型类型指定,默认为 Object
// 4. 如果是这样写 泛型默认是 Object
ArrayList arrayList = new ArrayList(); // 等价于 ArrayList<Object> arrayList = new ArrayList<Object>();
/*
public boolean add(Object e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
*/
// 创建一个 Tiger 对象
Tiger tiger = new Tiger(); // 没有指定泛型类型,默认为 Object
}
}
class Tiger<E> { // 类 E 是泛型类型
E e;
public Tiger() {
}
public Tiger(E e) {
this.e = e;
}
}
class A {
}
class B extends A {
}
class Pig<E> { // Pig 使用泛型
E e;
public Pig(E e) {
this.e = e;
}
public void f() {
System.out.println(e.getClass()); // 输出泛型对象的运行时类型
}
}
/**
* @创建人 wdl
* @创建时间 2024/9/14
* @描述
*/
import java.util.*;
/**
* @author 韩顺平
* @version 1.0
*/
@SuppressWarnings({"all"})
public class GenericExercise02 {
public static void main(String[] args) {
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee("tom", 20000, new MyDate(1980, 12, 11)));
employees.add(new Employee("jack", 12000, new MyDate(2001, 12, 12)));
employees.add(new Employee("tom", 50000, new MyDate(1980, 12, 10)));
System.out.println("employees=" + employees);
// 定制排序
employees.sort(new Comparator<Employee>() {
//没有泛型的时候需要这样写
// @Override
// public int compare(Object o1, Object o2) {
// return 0;
// }
@Override
public int compare(Employee emp1, Employee emp2) {
// 先按照 name 排序,如果 name 相同,则按生日日期的先后排序。【即:定制排序】
// 先对传入的参数进行验证
if (!(emp1 instanceof Employee && emp2 instanceof Employee)) {
System.out.println("类型不正确..");
return 0;
}
// 比较 name
int i = emp1.getName().compareTo(emp2.getName());
if (i != 0) {
return i;
}
// 下面是对 birthday 的比较,因此,我们最好把这个比较,放在 MyDate 类完成
// 封装后,将来可维护性和复用性,就大大增强.
return emp1.getBirthday().compareTo(emp2.getBirthday());
}
});
System.out.println("==对雇员进行排序==");
System.out.println(employees);
}
}
/**
* 定义 Employee 类
* 1) 该类包含:private 成员变量 name,sal,birthday,其中 birthday 为 MyDate 类的对象;
* 2) 为每一个属性定义 getter, setter 方法;
* 3) 重写 toString 方法输出 name, sal, birthday
*/
class Employee {
private String name;
private double sal;
private MyDate birthday;
public Employee(String name, double sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Employee{name='" + name + '\'' + ", sal=" + sal + ", birthday=" + birthday + '}';
}
}
/**
* 4) MyDate 类包含: private 成员变量 month,day,year;并为每一个属性定义 getter, setter 方法;
*/
class MyDate implements Comparable<MyDate> {
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
@Override
public int compareTo(MyDate other) {
// 比较 year
int yearDiff = this.year - other.year;
if (yearDiff != 0) {
return yearDiff;
}
// 比较 month
int monthDiff = this.month - other.month;
if (monthDiff != 0) {
return monthDiff;
}
// 比较 day
return this.day - other.day;
}
@Override
public String toString() {
return "MyDate{year=" + year + ", month=" + month + ", day=" + day + '}';
}
}
自定义泛型
自定义泛型类(类型是在创建对象时
确定的)
class 类名<T, R...>{//...表示可以有多个泛型
成员
}
- 普通成员可以使用泛型(属性、方法),
但使用泛型变量不能初始化
(因为对象没有创建的时候不确定其具体的类型) - 泛型类的
类型,是在创建对象时确定的
(静态方法/静态变量/静态代码块 中不能使用类的泛型)(因为创建对象时,需要指定确定类型), 如果在创建对象时,没有指定类型,默认为Object
import java.util.Arrays;
/**
* @创建人 wdl
* @创建时间 2024/9/14
* @描述
*/
@SuppressWarnings({"all"})
public class CustomGeneric_ {
public static void main(String[] args) {
// T=Double, R=String, M=Integer
Tiger<Double, String, Integer> g = new Tiger<>("john");
g.setT(10.9); // OK
// g.setT("yy"); // 错误,类型不对
System.out.println(g);
Tiger g2 = new Tiger("john~~"); // OK T=Object, R=Object, M=Object
g2.setT("yy"); // OK ,因为 T=Object,"yy"=String 是 Object 子类
// g2.setT(1);
System.out.println("g2=" + g2);
}
}
// 解读
// 1. Tiger 后面泛型,所以我们把 Tiger 就称为自定义泛型类
// 2. T, R, M 泛型的标识符, 一般是单个大写字母
// 3. 泛型标识符可以有多个.
// 4. 普通成员可以使用泛型 (属性、方法)
// 5. 使用泛型的数组,不能初始化
// 6. 静态方法中不能使用类的泛型
class Tiger<T, R, M> {
String name;
R r; // 属性使用到泛型
M m;
T t;
// 因为数组在 new 时无法确定 T 的类型,无法在内存开空间
T[] ts;
public Tiger(String name) {
this.name = name;
}
public Tiger(R r, M m, T t) { // 构造器使用泛型
this.r = r;
this.m = m;
this.t = t;
}
public Tiger(String name, R r, M m, T t) { // 构造器使用泛型
this.name = name;
this.r = r;
this.m = m;
this.t = t;
}
// 因为静态是和类相关的,在类加载时,对象还没有创建
// 所以,如果静态方法和静态属性使用了泛型,JVM 就无法完成初始化
// static R r2;
// public static void m1(M m) {
//
// }
// 方法使用泛型
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public R getR() {
return r;
}
public void setR(R r) { // 方法使用到泛型
this.r = r;
}
public M getM() { // 返回类型可以使用泛型
return m;
}
public void setM(M m) {
this.m = m;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
@Override
public String toString() {
return "Tiger{" +
"name='" + name + '\'' +
", r=" + r +
", m=" + m +
", t=" + t +
", ts=" + Arrays.toString(ts) +
'}';
}
}
自定义泛型接口(类型是在继承接口或者实现接口时
确定的)
interface 接口名<T, R...>{
}
- 接口中,静态…也不能使用泛型(这个和泛型类规定一样)
- 泛型接口的类型,
在继承接口或者实现接口时确定
- 没有指定类型,默认为Object
/**
* @创建人 wdl
* @创建时间 2024/9/14
* @描述
*/
public class CustomInterfaceGeneric {
public static void main(String[] args) {
// 测试 AA 实现类
AA aa = new AA();
System.out.println(aa.get("test")); // null
aa.hi(3.14);
aa.run(1.0, 2.0, "A", "B");
// 测试 BB 实现类
BB bb = new BB();
System.out.println(bb.get(10)); // null
bb.hi(4.5f);
bb.run(1.1f, 2.2f, 3, 4);
// 测试 CC 实现类
CC cc = new CC();
System.out.println(cc.get("object")); // null
cc.hi("hello");
cc.run(1, 2, "u1", "u2");
}
}
/**
* 泛型接口使用的说明
* 1. 接口中,静态成员也不能使用泛型
* 2. 泛型接口的类型, 在继承接口或者实现接口时确定
* 3. 没有指定类型,默认为 Object
*/
// 在继承接口时指定泛型接口的类型
interface IA extends IUsb<String, Double> {}
// 当我们去实现 IA 接口时,因为 IA 在继承 IUsb 接口时,指定了 U 为 String,R 为 Double
// 在实现 IUsb 接口的方法时,使用 String 替换 U,使用 Double 替换 R
class AA implements IA {
@Override
public Double get(String s) {
System.out.println("AA.get() called with: " + s);
return null;
}
@Override
public void hi(Double aDouble) {
System.out.println("AA.hi() called with: " + aDouble);
}
@Override
public void run(Double r1, Double r2, String u1, String u2) {
System.out.println("AA.run() called with: " + r1 + ", " + r2 + ", " + u1 + ", " + u2);
}
}
// 实现接口时,直接指定泛型接口的类型
// 给 U 指定 Integer 给 R 指定 Float
// 所以,当我们实现 IUsb 方法时,会使用 Integer 替换 U,使用 Float 替换 R
class BB implements IUsb<Integer, Float> {
@Override
public Float get(Integer integer) {
System.out.println("BB.get() called with: " + integer);
return null;
}
@Override
public void hi(Float aFloat) {
System.out.println("BB.hi() called with: " + aFloat);
}
@Override
public void run(Float r1, Float r2, Integer u1, Integer u2) {
System.out.println("BB.run() called with: " + r1 + ", " + r2 + ", " + u1 + ", " + u2);
}
}
// 没有指定类型,默认为 Object
// 等价于 class CC implements IUsb<Object, Object>
class CC implements IUsb<Object, Object> {
@Override
public Object get(Object o) {
System.out.println("CC.get() called with: " + o);
return null;
}
@Override
public void hi(Object o) {
System.out.println("CC.hi() called with: " + o);
}
@Override
public void run(Object r1, Object r2, Object u1, Object u2) {
System.out.println("CC.run() called with: " + r1 + ", " + r2 + ", " + u1 + ", " + u2);
}
}
// 泛型接口定义
interface IUsb<U, R> {
// int n ; //
// U name; //不能这样使用,因为接口里面的变量都是pulbic static final类型的
// 普通方法中,可以使用接口泛型
R get(U u);
void hi(R r);
void run(R r1, R r2, U u1, U u2);
// 在 jdk8 中,可以在接口中,使用默认方法, 也是可以使用泛型
default R method(U u) {
return null;
}
}
自定义泛型方法(类型是在泛型方法被调用时
确定的)
修饰符 <T,R..>返回类型 方法名(参数列表){
}
- 泛型方法,可以定义在普通类中,也可以定义在泛型类
中 - 当泛型方法被调用时,类型会确定
- public void eat(E e)},修饰符后没有<T,R…>eat方法不是泛型方法,而是使用了泛型
import java.util.ArrayList;
/**
* @创建人 wdl
* @创建时间 2024/9/14
* @描述
*/
@SuppressWarnings({"all"})
public class CustomMethodGeneric {
public static void main(String[] args) {
Car car = new Car();
car.fly("宝马", 100); // 当调用方法时,传入参数,编译器就会确定类型
System.out.println("=======");
car.fly(300, 100.1); // 当调用方法时,传入参数,编译器就会确定类型
// 测试泛型类 Fish
// T->String, R->ArrayList
Fish<String, ArrayList> fish = new Fish<>();
fish.hello(new ArrayList<>(), 11.3f);
}
}
// 泛型方法可以定义在普通类中,也可以定义在泛型类中
class Car { // 普通类
public void run() { // 普通方法
System.out.println("Car is running...");
}
// 泛型方法
// 1. <T,R> 就是泛型
// 2. 是提供给 fly 使用的
public <T, R> void fly(T t, R r) { // 泛型方法
System.out.println("t type: " + t.getClass().getSimpleName()); // 打印 t 的类型
System.out.println("r type: " + r.getClass().getSimpleName()); // 打印 r 的类型
}
}
// 泛型类
class Fish<T, R> { // 泛型类
public void run() { // 普通方法
System.out.println("Fish is swimming...");
}
// 泛型方法
public <U, M> void eat(U u, M m) { // 泛型方法
System.out.println("Eating: " + u + " and " + m);
}
// 说明
// 1. 下面 hi 方法不是泛型方法
// 2. 是 hi 方法使用了类声明的泛型
public void hi(T t) {
System.out.println("Hi: " + t);
}
// 泛型方法,可以使用类声明的泛型,也可以使用自己声明的泛型
public <K> void hello(R r, K k) {
System.out.println("R type: " + r.getClass().getSimpleName());
System.out.println("K type: " + k.getClass().getSimpleName());
// System.out.println("K type: " + k.getClass());
}
}
泛型的继承和通配符
- 泛型不具备继承性List
<Object>
list = new ArrayList(); // 错误 - <?>:支持任意泛型类型
- <?extends A>:支持A类以及A类的子类,规定了泛型的上限
- <?super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
import java.util.ArrayList;
import java.util.List;
/**
* @创建人 wdl
* @创建时间 2024/9/14
* @描述
*/
public class GenericExtends {
public static void main(String[] args) {
Object o = new String("xx");
// 泛型没有继承性
// List<Object> list = new ArrayList<String>();
// 举例说明下面三个方法的使用
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<AA> list3 = new ArrayList<>();
List<BB> list4 = new ArrayList<>();
List<CC> list5 = new ArrayList<>();
// 如果是 List<?> c ,可以接受任意的泛型类型
printCollection1(list1);
printCollection1(list2);
printCollection1(list3);
printCollection1(list4);
printCollection1(list5);
// List<? extends AA> c: 表示 上限,可以接受 AA 或者 AA 子类
// printCollection2(list1);//×
// printCollection2(list2);//×
printCollection2(list3);//√
printCollection2(list4);//√
printCollection2(list5);//√
// List<? super AA> c: 支持 AA 类以及 AA 类的父类,不限于直接父类
printCollection3(list1);//√
// printCollection3(list2);//×
printCollection3(list3);//√
// printCollection3(list4);//×
// printCollection3(list5);//×
// 其他排序相关代码可以根据需要在此添加
}
// ? extends AA 表示 上限,可以接受 AA 或者 AA 子类
public static void printCollection2(List<? extends AA> c) {
for (Object object : c) {
System.out.println(object);
}
}
// 说明: List<?> 表示 任意的泛型类型都可以接受
public static void printCollection1(List<?> c) {
for (Object object : c) { // 通配符,取出时,就是 Object
System.out.println(object);
}
}
// ? super 子类类名 AA:支持 AA 类以及 AA 类的父类,不限于直接父类,
// 规定了泛型的下限
public static void printCollection3(List<? super AA> c) {
for (Object object : c) {
System.out.println(object);
}
}
}
// 定义 AA, BB, CC 类
class AA {
}
class BB extends AA {
}
class CC extends BB {
}
JUnit
- 一个类有很多功能代码需要测试,为了测试,就需要写入到main方法中。如果有多个功能代码测试,就需要来回注销,切换很麻烦,如果可以直接运行一个方法,就方便很多,并且可以给出相关信息,就好了->JUnit
- JUnit是一个Java语言的单元测试框架,多数Java的开发环境都已经集成了JUnit作为单元测试的工具
/**
* @创建人 wdl
* @创建时间 2024/9/14
* @描述
*/
// 导入需要的 JUnit 包
import org.junit.Test;
public class JUnitTestExample {
public static void main(String[] args) {
// 传统方式手动调用方法(不是通过 JUnit 框架)
System.out.println("传统方式调用方法:");
new JUnitTestExample().m1();
new JUnitTestExample().m2();
}
// 使用 JUnit 的方式运行测试方法
@Test
public void m1() {
System.out.println("m1 方法被调用");
}
@Test
public void m2() {
System.out.println("m2 方法被调用");
}
@Test
public void m3() {
System.out.println("m3 方法被调用");
}
}
IO流
文件(保存数据的地方)
文件流
创建文件对象相关构造器和方法(先有File对象,然后用其createNewFile方法创建新文件)
new File(String pathname) //根据路径构建一个File对象
new File(File parent,String child) //根据父目录文件+子路径构建
new File(String parent,String child) //根据父目录+子路径构建
import org.junit.Test;
import java.io.File;
import java.io.IOException;
/**
* @创建人 wdl
* @创建时间 2024/9/21
* @描述
*/
public class FileCreate {
public static void main(String[] args) {
// 这里可以手动调用测试方法来创建文件
FileCreate fileCreate = new FileCreate();
fileCreate.create01();
fileCreate.create02();
fileCreate.create03();
}
// 方式 1: 使用 new File(String pathname)
@Test
public void create01() {
String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
File file = new File(filePath);
try {
if (file.createNewFile()) {
System.out.println("文件创建成功");
} else {
System.out.println("文件已存在");
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 方式 2: 使用 new File(File parent, String child)
// 根据父目录文件+子路径构建
@Test
public void create02() {
File parentFile = new File("D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\");
String fileName = "news2.txt";
// 创建文件对象
File file = new File(parentFile, fileName);
try {
if (file.createNewFile()) {
System.out.println("文件创建成功");
} else {
System.out.println("文件已存在");
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 方式 3: 使用 new File(String parent, String child)
// 根据父目录+子路径构建
@Test
public void create03() {
String parentPath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\";
String fileName = "news4.txt";
// 创建文件对象
File file = new File(parentPath, fileName);
try {
if (file.createNewFile()) {
System.out.println("文件创建成功");
} else {
System.out.println("文件已存在");
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 下面四个都是抽象类
// InputStream
// OutputStream
// Reader // 字符输入流
// Writer // 字符输出流
}
获取文件的相关信息
getName
getAbsolutePath
getParent
length
exists
isFile
isDirectory
import org.junit.Test;
import java.io.File;
import java.io.IOException;
/**
* @创建人 wdl
* @创建时间 2024/9/21
* @描述
*/
public class FileInformation {
public static void main(String[] args) {
// 手动调用 info() 方法来查看文件信息
FileInformation fileInformation = new FileInformation();
fileInformation.info();
}
// 获取文件的信息
@Test
public void info() {
// 先创建文件对象
File file = new File("D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt");
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
// 调用相应的方法,得到对应信息
System.out.println("文件名字 = " + file.getName());
System.out.println("文件绝对路径 = " + file.getAbsolutePath());
System.out.println("文件父级目录 = " + file.getParent());
System.out.println("文件大小(字节) = " + file.length());
System.out.println("文件是否存在 = " + file.exists()); // T
System.out.println("是不是一个文件 = " + file.isFile()); // T
System.out.println("是不是一个目录 = " + file.isDirectory()); // F
}
}
目录的操作和文件删除
mkdir创建一级目录
mkdirs创建多级目录
delete删除空目录或文件
I/O流原理及流的分类(对于数据的输入/输出操作以”流(stream)”的方式进行)
-
按操作数据单位不同分为
:字节流(8 bit)二进制文件,字符流(按字符)文本文件 - 按数据流的流向不同分为:输入流,输出流
- 按流的角色的不同分为:节点流,处理流/包装流
FileInputStream(字节流)(其不直接处理字符编码
,如果使用它们写入和读取字节数据,而不考虑编码格式,那么就有可能导致乱码(见转换流))
/**
* @创建人 wdl
* @创建时间 2024/9/21
* @描述
*/
import org.junit.Test;
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamExample {
public static void main(String[] args) {
}
/**
* 演示读取文件...
* 单个字节的读取,效率比较低
* -> 使用 read(byte[] b)
*/
@Test
public void readFile01() {
String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
int readData;
FileInputStream fileInputStream = null;
try {
// 创建 FileInputStream 对象,用于读取文件
fileInputStream = new FileInputStream(filePath);
// 从该输入流读取一个字节的数据。如果没有输入可用,此方法将阻止。
// 如果返回-1, 表示读取完毕
while ((readData = fileInputStream.read()) != -1) {
System.out.print((char) readData); // 转成 char 显示
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭文件流,释放资源.
try {
if (fileInputStream != null) {
fileInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 使用 read(byte[] b) 读取文件,提高效率
*/
@Test
public void readFile02() {
String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
byte[] buf = new byte[8]; // 一次读取 8 个字节.
int readLen;
FileInputStream fileInputStream = null;
try {
// 创建 FileInputStream 对象,用于读取文件
fileInputStream = new FileInputStream(filePath);
// 从该输入流读取最多 b.length 字节的数据到字节数组。此方法将阻塞,直到某些输入可用。
// 如果返回-1, 表示读取完毕
// 如果读取正常, 返回实际读取的字节数
while ((readLen = fileInputStream.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen)); // 显示
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭文件流,释放资源.
try {
if (fileInputStream != null) {
fileInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileOutputStream
写文件
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStream01 {
public static void main(String[] args) {
//创建 FileOutputStream 对象
String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
FileOutputStream fileOutputStream = null;
try {
// 得到 FileOutputStream 对象 对象
// 老师说明
// 1. new FileOutputStream(filePath) 创建方式,当写入内容时,会覆盖原来的内容
// 2. new FileOutputStream(filePath, true) 创建方式,当写入内容时,是追加到文件后面
fileOutputStream = new FileOutputStream(filePath, true);
// 写入字符串
String str = "呜我大,world!";
// str.getBytes() 可以把 字符串-> 字节数组
/*
* write(byte[] b, int off, int len)
* 将 len 字节从位于偏移量 off 的指定字节数组写入此文件输出流
*/
fileOutputStream.write(str.getBytes(), 0, 3);
System.out.println("数据写入成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 关闭文件输出流,释放资源
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
复制文件
/**
* @创建人 wdl
* @创建时间 2024/9/21
* @描述
*/
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) {
// 完成文件拷贝,将 e:\\Koala.jpg 拷贝到 c:\\
// 思路分析
// 1. 创建文件的输入流 , 将文件读入到程序
// 2. 创建文件的输出流,将读取到的文件数据,写入到指定的文件。
String srcFilePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
String destFilePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news2.txt";
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
// 创建文件输入流对象,用于读取源文件
fileInputStream = new FileInputStream(srcFilePath);
// 创建文件输出流对象,用于写入目标文件
fileOutputStream = new FileOutputStream(destFilePath);
// 定义一个字节数组,提高读取效率
byte[] buf = new byte[1024];
int readLen;
while ((readLen = fileInputStream.read(buf)) != -1) {
// 读取到后,就写入到文件通过 fileOutputStream
// 即,是一边读,一边写
fileOutputStream.write(buf, 0, readLen); // 一定要使用这个方法
}
System.out.println("拷贝 ok~");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 关闭输入流和输出流,释放资源
if (fileInputStream != null) {
fileInputStream.close();
}
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileReader(字符流)(不会乱码)
- new FileReader(File/String)
- read:每次读取单个字符,返回该字符,如果到文件未尾返回-1
- read(char[]):批量读取多个字符到数组,返回读取到的字符数,如果到文件未尾返回-1
相关API:
- new String(char[]):将char[]转换成String
- new String(char[],off,len):将char[]的指定部分转换成String
/**
* @创建人 wdl
* @创建时间 2024/9/21
* @描述
*/
import org.junit.Test;
import java.io.FileReader;
import java.io.IOException;
public class FileReader_ {
public static void main(String[] args) {
}
/**
* 单个字符读取文件
*/
@Test
public void readFile01() {
String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
FileReader fileReader = null;
int data = 0;
// 1. 创建 FileReader 对象
try {
fileReader = new FileReader(filePath);
// 循环读取 使用 read, 单个字符读取
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 字符数组读取文件
*/
@Test
public void readFile02() {
System.out.println("~~~readFile02 ~~~");
String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
FileReader fileReader = null;
int readLen = 0;
char[] buf = new char[8];
// 1. 创建 FileReader 对象
try {
fileReader = new FileReader(filePath);
// 循环读取 使用 read(buf), 返回的是实际读取到的字符数
// 如果返回-1, 说明到文件结束
while ((readLen = fileReader.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileWriter(FileWriter使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件!)
- new FileWriter(File/String):覆盖模式,相当于流的指针在首端
- new FileWriter(File/String,true):追加模式,相当于流的指针在尾端
- write(int):写入单个字符
- write(char[]):写入指定数组
- write(char[],off,len):写入指定数组的指定部分
- write(string):写入整个字符串7)write(string,off,len):写入字符串的指定部分相关APl:String类:toCharArray:将String转换成char[]
import java.io.FileWriter;
public class FileWriter_ {
public static void main(String[] args) {
String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
// 创建 FileWriter 对象
FileWriter fileWriter = null; // 需要导入 java.io 包
char[] chars = {'a', 'b', 'c'};
try {
fileWriter = new FileWriter(filePath); // 默认是覆盖写入
// 3) write(int): 写入单个字符
fileWriter.write('H');
// 4) write(char[]): 写入指定数组
fileWriter.write(chars);
// 5) write(char[], off, len): 写入指定数组的指定部分
fileWriter.write("教育1111111".toCharArray(), 0, 3);
// 6) write(String): 写入整个字符串
fileWriter.write(" 你好北京~");
fileWriter.write("风雨之后,定见彩虹");
// 7) write(String, off, len): 写入字符串的指定部分
fileWriter.write("上海天津", 0, 2);
// 在数据量大的情况下,可以使用循环操作.
} catch (java.io.IOException e) { // 需要导入 java.io 包
e.printStackTrace();
} finally {
// 对应 FileWriter , 一定要关闭流,或者 flush 才能真正的把数据写入到文件
// 看源码就知道原因.
/*
看看代码
private void writeBytes() throws IOException {
this.bb.flip();
int var1 = this.bb.limit();
int var2 = this.bb.position();
assert var2 <= var1;
int var3 = var2 <= var1 ? var1 - var2 : 0;
if (var3 > 0) {
if (this.ch != null) {
assert this.ch.write(this.bb) == var3 : var3;
} else {
this.out.write(this.bb.array(), this.bb.arrayOffset() + var2, var3);
}
}
this.bb.clear();
}
*/
try {
fileWriter.flush();
// 关闭文件流,等价 flush() + 关闭
fileWriter.close();
} catch (java.io.IOException e) {
e.printStackTrace();
}
}
System.out.println("程序结束...");
}
}
节点流(可以从一个特定的数据源读写数据)和处理流(也叫(包装流)是“连接”在已存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能)
- 节点流是底层流/低级流,
直接跟数据源相接
。 - 处理流(包装流)包装节点流,
既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。
- 处理流(也叫包装流)对节点流进行包装,
使用了修饰器设计模式
,不会直接与数据源相连[模拟修饰器设计模式]
处理流-BufferedInputStream 和 BufferedOutputStream
import java.io.*;
/**
* @创建人 wdl
* @创建时间 2024/9/21
* @描述
*/
public class BufferedCopy02 {
public static void main(String[] args) {
// String srcFilePath = "e:\\Koala.jpg";
// String destFilePath = "e:\\hsp.jpg";
// String srcFilePath = "e:\\0245_韩顺平零基础学 Java_引出 this.avi";
// String destFilePath = "e:\\hsp.avi";
String srcFilePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
String destFilePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news3.txt";
// 创建 BufferedOutputStream 对象 BufferedInputStream 对象
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// 因为 FileInputStream 是 InputStream 子类(向上类型转换)
bis = new BufferedInputStream(new FileInputStream(srcFilePath));
bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
// 循环的读取文件,并写入到 destFilePath
byte[] buff = new byte[1024];
int readLen = 0;
// 当返回 -1 时,就表示文件读取完毕
while ((readLen = bis.read(buff)) != -1) {
bos.write(buff, 0, readLen);
}
System.out.println("文件拷贝完毕~~~");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流 , 关闭外层的处理流即可,底层会去关闭节点流
try {
if (bis != null) {
bis.close();
}
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
处理流-BufferedReader 和 BufferedWriter
import java.io.*;
/**
* @创建人 wdl
* @创建时间 2024/9/21
* @描述
*/
public class BufferedCopy_ {
public static void main(String[] args) {
// 说明
// 1. BufferedReader 和 BufferedWriter 是按字符操作
// 2. 不要去操作 二进制文件[声音,视频,doc, pdf], 可能造成文件损坏
// BufferedInputStream
// BufferedOutputStream
String srcFilePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
String destFilePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news4.txt";
// String srcFilePath = "e:\\0245_韩顺平零基础学 Java_引出 this.avi";
// String destFilePath = "e:\\a2 韩顺平.avi";
BufferedReader br = null;
BufferedWriter bw = null;
String line;
try {
br = new BufferedReader(new FileReader(srcFilePath));
bw = new BufferedWriter(new FileWriter(destFilePath));
// 说明: readLine 读取一行内容,但是没有换行
while ((line = br.readLine()) != null) {
// 每读取一行,就写入
bw.write(line);
// 插入一个换行
bw.newLine();
}
System.out.println("拷贝完毕...");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
try {
if (br != null) {
br.close();
}
if (bw != null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
对象流-ObjectInputStream (提供 反序列化功能)和 ObjectOutputStream(提供 序列化功能)
- 序列化和反序列化序列化就是在保存数据时,但保存数据的值和数据类型
- 反序列化就是在恢复数据时,恢复数据的值和数据类型
- 需要让某个对象支持序列化机制,则必须让其类是可序列化的(1. 读写顺序要一致;2. 序列化的类中建议添加SerialVersionUlD,为了提高版本的兼容性序列化对象时,默认将里面所有属性都进行序列化,
但除了static或transient修饰的成员;
3. 序列化对象时,要求里面属性的类型也需要实现序列化接口;4. 序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化),为了让某个类是可序列化的,该类必须实现如下两个接口之一:
- Serializable //这是一个标记接口,没有方法(Java 的序列化机制就会自动处理这个类的对象的序列化和反序列化过程)
- Externalizable // 该接口有方法需要实现,
因此我们一般实现上面的 Serializable接口
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
// 演示 ObjectOutputStream 的使用, 完成数据的序列化
public class ObjectOutStream_ {
public static void main(String[] args) throws Exception {
// 序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存
// String filePath = "e:\\data.dat";
String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
// 序列化数据
oos.writeInt(100); // int -> Integer (实现了 Serializable)
oos.writeBoolean(true); // boolean -> Boolean (实现了 Serializable)
oos.writeChar('a'); // char -> Character (实现了 Serializable)
oos.writeDouble(9.5); // double -> Double (实现了 Serializable)
oos.writeUTF("韩顺平教育"); // String
// 保存一个 dog 对象
oos.writeObject(new Dog("旺财", 10, "日本", "白色"));
oos.close();
System.out.println("数据保存完毕(序列化形式)");
}
}
class Dog implements Serializable {
private String name;
private int age;
private String breed;
private String color;
public Dog(String name, int age, String breed, String color) {
this.name = name;
this.age = age;
this.breed = breed;
this.color = color;
}
// 可选:添加 getter 和 setter 方法,或者 toString() 方法用于调试
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", breed='" + breed + '\'' +
", color='" + color + '\'' +
'}';
}
}
/**
* @创建人 wdl
* @创建时间 2024/9/21
* @描述
*/
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
// 反序列化示例
public class ObjectInputStream_ {
public static void main(String[] args) throws Exception {
// 1.创建流对象
String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news1.txt";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
// 2.读取,注意顺序
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
System.out.println(ois.readObject()); // 输出 Dog 对象
// 以下两行代码将导致异常,因为 data.dat 文件中没有足够的对象可供读取
// System.out.println(ois.readObject());
// System.out.println(ois.readObject());
// 3.关闭
ois.close();
System.out.println("以反序列化的方式读取(恢复)ok~");
}
}
标准输入输出流
转换流-InputStreamReader 和OutputStreamWriter
问题引入-中文乱码问题
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
/**
* @创建人 wdl
* @创建时间 2024/9/22
* @描述
*/
public class CodeQuestion {
public static void main(String[] args) throws IOException {
// 读取 e:\\a.txt 文件到程序
// 思路
// 1. 创建字符输入流 BufferedReader [处理流]
// 2. 使用 BufferedReader 对象读取 a.txt
// 3. 默认情况下,读取文件是按照 utf-8 编码
String filePath = "e:\\a.txt";
BufferedReader br = new BufferedReader(new FileReader(filePath));
String s = br.readLine();
System.out.println("读取到的内容: " + s);
br.close();
// InputStreamReader
// OutputStreamWriter
}
}
解决办法
- InputStreamReader:Reader的子类,可以将InputStream(字节流)包装成(转换)Reader(字符流);OutputStreamWriter:Writer的子类,实现将OutputStream(字节流)包装成Writer(字符流)
- 当处理纯文本数据时,
使用字符流效率更高
,并且可以有效解决中文问题
,所以建议将字节流转换成字符流 - 可以在使用时指定编码格式(比如 utf-8,gbk,gb2312,ISO8859-1 等)
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class InputStreamReader_ {
public static void main(String[] args) throws IOException {
String filePath = "D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\news4.txt";
// 解读
// 1. 把 FileInputStream 转成 InputStreamReader
// 2. 指定编码 gbk
// InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
// 3. 把 InputStreamReader 传入 BufferedReader
// BufferedReader br = new BufferedReader(isr);
// 将 2 和 3 合在一起
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(filePath), "utf-8"));
// 4. 读取
String s = br.readLine();
System.out.println("读取内容=" + s);
// 5. 关闭外层流
br.close();
}
}
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class OutputStreamWriter_ {
public static void main(String[] args) throws IOException {
// 1. 创建流对象
OutputStreamWriter osw =
new OutputStreamWriter(new FileOutputStream("d:\\a.txt"), "gbk");
// 第 857页
// 韩顺平循序渐进学 Java 零基础
// 2. 写入
osw.write("hello,韩顺平教育~");
// 3. 关闭
osw.close();
System.out.println("保存成功~");
}
}
Properties 类
看一个需求
如下一个配置文件 mysql.properties
ip=192.168.0.13
user=root
pwd=12345
请问编程读取 ip 、user 和 pwd 的值是多少
传统方法
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
/**
* @创建人 wdl
* @创建时间 2024/9/22
* @描述
*/
public class Properties01 {
public static void main(String[] args) throws IOException {
// 获取当前工作目录
String currentDir = System.getProperty("user.dir");
System.out.println(currentDir);
// 读取 mysql.properties 文件,并得到 ip, user 和 pwd
BufferedReader br = new BufferedReader(new FileReader("MyTest\\\\src\\\\mysql.properties"));
// BufferedReader br = new BufferedReader(new FileReader("D:\\2024JavaReview\\【零基础 快速学Java】韩顺平 零基础30天学会Java\\CODE\\JavaHSP-main\\MyTest\\src\\mysql.properties"));
String line = "";
while ((line = br.readLine()) != null) { // 循环读取
String[] split = line.split("=");
// 如果我们要求指定的 ip 值
if ("ip".equals(split[0])) {
System.out.println(split[0] + "值是: " + split[1]);
}
System.out.println(Arrays.toString(split));
}
br.close();
}
}
使用Properties类可以方便实现
- 专门用于读写配置文件的集合类配置文件的格式:
键=值
键=值 - 注意:键值对不需要有空格,值不需要用引号引起来。
默认类型是String
- Properties的常见方法
load:加载配置文件的键值对到Properties对象
Iist:将数据显示到指定设备
getProperty(key):根据键获取值
setProperty(key,value):设置键值对到Properties对象
store:将Properties中的键值对存储到配置文件,在idea 中,保存信息到配置文件,如果含有中文,会存储为unicode码
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class CombinedProperties {
public static void main(String[] args) throws IOException {
// 调用 Properties02 的逻辑
runProperties02();
// 调用 Properties03 的逻辑
runProperties03();
}
public static void runProperties02() throws IOException {
//使用 Properties 类来读取 mysql.properties 文件
//1. 创建 Properties 对象
Properties properties = new Properties();
//2. 加载指定配置文件
properties.load(new FileReader("MyTest\\src\\mysql.properties"));
//3. 把 k-v 显示控制台
properties.list(System.out);
//4. 根据 key 获取对应的值
String user = properties.getProperty("user");
String pwd = properties.getProperty("pwd");
System.out.println("用户名=" + user);
System.out.println("密码是=" + pwd);
}
public static void runProperties03() throws IOException {
//使用 Properties 类来创建 配置文件, 修改配置文件内容
Properties properties = new Properties();
//创建
//1.如果该文件没有 key 就是创建
//2.如果该文件有 key ,就是修改
/*
Properties 父类是 Hashtable , 底层就是 Hashtable 核心方法
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;//如果 key 存在,就替换
return old;
}
}
addEntry(hash, key, value, index);//如果是新 k, 就 addEntry
return null;
}
*/
properties.setProperty("charset", "utf8");
properties.setProperty("user", "汤姆");//注意保存时,是中文的 unicode 码值
properties.setProperty("pwd", "888888");
//将 k-v 存储文件中即可
properties.store(new FileOutputStream("MyTest\\src\\mysql2.properties"), null);
System.out.println("保存配置文件成功~");
}
}