一、File类
1、File类的定义
File类当作一种特殊类型的文件,即文件名单列表,只能代表一个文件,不能访问文件的内容。只能对文件和目录进行新建、删除、重名名等操作。
2、创建File对象
构造方法:File(String pathname),抽象路径应尽量使用相对路径
3、相对路径和绝对路径
- 绝对路径:无论当前工作目录如何,始终指向文件系统中的相同位置的路径(路径以盘符或/开头)
- 相对路径:从某个给定的工作目录开始到目标位置的路径(路径不能以盘符或/开头)
/**
* @document: File对象创建,File可以表示一个文件或者目录
* @Author:SmallG
* @CreateTime:2023/8/2+14:06
*/
public class Demo01 {
public static void main(String[] args) {
//指定文件路径 相对路径
File file = new File("src/day03/demo.txt");
//获取文件名
String fileName = file.getName();
System.out.println(fileName); //demo.txt
//查看文件大小 单位字节
long length = file.length();
System.out.println(length); //37
//文件是否可读
boolean canRead = file.canRead();
boolean canWrite = file.canWrite(); // 文件是否可写
System.out.println("可读:" + canRead);
System.out.println("可写:" + canWrite);
//查看是否被隐藏
System.out.println("是否被隐藏:" + file.isHidden());
}
}
4、File类的常用操作
- length():返回文件的长度,即占用的空间大小
- exists():测试路径是否存在,返回值是boolean类型
- createNewFile():当文件不存在时,自动创建文件,返回值是boolean类型
- delete():删除此抽象路径名表示的文件或目录,返回值是boolean类型,删除目录时,要保证是空目录才能成功删除。删除时比较暴力,直接删除回收站里不会留存。
/**
* @document: File 的常用操作
* @Author:SmallG
* @CreateTime:2023/8/2+14:35
*/
public class Demo02 {
public static void main(String[] args) throws IOException {
//创建一个不存在的文件
File file = new File("F:/hello/hello.txt");
//判断文件是否存在
if (!file.exists()) { //如果不存在,创建文件
//createNewFile()方法,如果文件不存在就创建
boolean success = file.createNewFile(); //有返回值 boolean类型,true则代表创建成功
System.out.println(success);
} else {
System.out.println("文件已存在");
}
}
}
/**
* @document: 删除文件
* @Author:SmallG
* @CreateTime:2023/8/2+15:05
*/
public class Demo03 {
public static void main(String[] args) {
File file = new File("F:/hello/hello.txt");
if (file.exists()) {
System.out.println(file.delete());
} else {
System.out.println("文件不存在");
}
//删除目录
File file1 = new File("F:/hello");
if (file1.exists()) {
System.out.println(file1.delete());
}
}
}
5、File创建目录
1、isDirectory() 方法:判断当前File表示的是否为一个目录,返回 boolean 类型
2、mkdir() 方法:
- 创建此抽象路径名指定的目录
- 当且仅当已创建目录时,返回 true;否则返回 false
3、mkdirs() 方法:
- 创建此抽象路径名指定的目录,包括所有必需但不存在的父目录
- 当且仅当已创建目录以及所有必需的父目录时,返回 true;否则返回 false
- 注意:此操作失败时也可能已经成功地创建了一部分必需的父目录
/**
* @document: 创建目录
* @Author:SmallG
* @CreateTime:2023/8/2+15:14
*/
public class Demo04 {
public static void main(String[] args) {
//在F盘下创建一个hello目录
File dir = new File("F:/hello");
//判断目录是否存在
if (!dir.exists()) {
//如果不存在,创建目录
boolean flag = dir.mkdir(); //返回一个boolean类型的值
System.out.println(flag); //true则目录创建成功
} else {
System.out.println("目录已存在");
}
//创建多级目录 F:/hello/world/d1/d2
File dirs = new File("F:/hello/world/d1/d2");
if (!dirs.exists()) {
boolean flag = dirs.mkdirs(); //返回一个boolean类型的值
System.out.println("多级目录创建" + flag);
} else {
System.out.println("目录已存在");
}
}
}
6、获取目录的所有子项
listFiles()方法,返回一个抽象录路径名数组,表示目录中的子项(文件或目录)
/**
* @document: 获取一个目录的所有子目录
* @Author:SmallG
* @CreateTime:2023/8/2+15:27
*/
public class Demo05 {
public static void main(String[] args) {
File dir = new File("src/day03");
//判断当前文件是否为目录
if (dir.isDirectory()) {
//获取一个目录中所有的子项
File[] files = dir.listFiles(); //返回一个数组
System.out.println("目录中的文件/目录数量:" + files.length);
//获取文件的路径
System.out.println(Arrays.toString(files));
} else {
System.out.println("当前不是一个目录");
}
}
}
7、方法的递归
/**
* @document: 递归运算 计算n的阶乘
* @Author:SmallG
* @CreateTime:2023/8/2+16:18
*/
public class Demo07 {
public static void main(String[] args) {
int n = 5;
//计算n的阶乘
System.out.println(factorial(n));
}
public static int factorial(int n) {
//递归终止条件 n == 1时,结束递归
if (n == 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
}
8、求文件夹的大小问题
文件夹大小=文件夹下各级文件中所有文件的大小之和
/**
* @document: 求文件的大小
* @Author:SmallG
* @CreateTime:2023/8/2+16:36
*/
public class Demo08 {
public static void main(String[] args) {
File file = new File("C:/Program Files/Internet Explorer");
long sum = getSize(file);
System.out.println("当前目录大小为:" + sum / 1024 / 1024 + "Mb"); //2Mb
}
public static long getSize(File file) {
//判断是不是文件
if (file.isFile()) {
return file.length();
} else {
//获取当前目录下的所有子项
long sum = 0;
File[] files = file.listFiles();
for (int i = 0; i < files.length; i++) {
sum += getSize(files[i]);
}
return sum;
}
}
}
9、FileFilter接口
用于抽象路径名的过滤器,此接口的实例可以传递给File类的listFiles(FileFilter)方法,用于返回满足该过滤器要求的子项
/**
* @document: FileFilter过滤器
* @Author:SmallG
* @CreateTime:2023/8/2+17:19
*/
public class Demo09 {
public static void main(String[] args) {
File dir = new File("C:/Program Files/Internet Explorer");
if (dir.isDirectory()) {
//创建过滤器规则 匿名内部类
FileFilter filter = new FileFilter() {
@Override
public boolean accept(File pathname) {
//先得到文件名
String fileName = pathname.getName();
System.out.println("正在过滤:" + fileName + "文件");
return fileName.startsWith("i"); //文件名是否以i开头 是的话返回true
}
};
File[] files = dir.listFiles(filter);
System.out.println("以i开头的数量:" + files.length);
System.out.println(Arrays.toString(files));
}
}
}
二、I/O流
1、什么是I/O流?
input和output,是输入输出流,流是一组有序的数据序列,也可以称为数据流。
文件保存在硬盘中,程序的运行都在内存中运行
到计算机内存中----->输入流
从内存中出来-------->输出流
2、I/O流分类
- 按使用方式:节点流与处理流,也称为基础流和高级流
- 按照数据流的方向:输入流、输出流
- 按处理数据单位:字节流、字符流
3、字节流
字节流是指数据流中的数据以字节为单位进行操作,主要用于处理二进制数据。流用完后要记得close()。
- InputStream和OutputStream:字节流的核心类,是两个抽象类,定义了基础的数据流读写方法,字节流中的其他类均为这两个类的子类。
- FileInputStream和FileOutputStream:字节流中最为常用的类,分别继承InputStream和OutputStream,属于基础流。
- BufferedInputStream和BufferedOutputStream:是字节流中较为常用的高级流,间接继承InputStream和OutputStream,主要提供了缓冲区功能。
(1)创建FOS对象
1.FileOutputStream:文件的字节输出流,可以以字节为单位将数据写入文件。如果指定文件已经包含内容,那么FOS对其写入数据时会将原有数据全部清除。
构造方法:
- FileOutputStream(File file):创建一个向指定File对象表示的文件中写数据的文件输出流
- FileOutputStream(String filename):创建一个向具有指定名称的文件中写数据的文件输出流
2.不清除原有数据的构造方法
- FileOutputStream(File file,boolean append):创建一个指向对象的文件写数据的文件输出流,若为true则在末尾添加。
- FileOutputStream(String filename,boolean append):创建一个指向名称的文件写数据的文件输出流,若为true则在末尾添加。
/**
* @document: 字节文件输出流
* @Author:SmallG
* @CreateTime:2023/8/3+9:16
*/
public class Demo10 {
public static void main(String[] args) throws IOException {
File file = new File("F:/hello.txt");
// FileOutputStream fos = new FileOutputStream(file); 传入文件名
//普通的创建方法 在写入时会将之前所有的内容覆盖 可以在append参数里面添加个true
FileOutputStream fos = new FileOutputStream("F:/hello.txt",true);
String str1 = "\n第一次写入内容:hello world";
//第二次写入内容
String str2 = "\n第二次写入内容:SmallG帅锅\n";
//字符串转换成字节数组
byte[] s1 = str1.getBytes(StandardCharsets.UTF_8); //utf-8统一字符编码
byte[] s2 = str2.getBytes();
fos.write(s1);
fos.write(s2);
fos.close(); //关闭流
}
}
(2)创建FIS对象
FileInputStream:文件的字节输入流,使用该流可以以字节为单位从文件中读取数据
构造方法:
- FileInputStream(File file):创建从指定File对象表示的文件中读取数据的文件输入流
- FileInputStream(String name):创建用于读取路径名所指的文件的文件输入流
/**
* @document: 字节文件输入流
* @Author:SmallG
* @CreateTime:2023/8/3+9:37
*/
public class Demo11 {
public static void main(String[] args) throws IOException {
// 创建fis对象
FileInputStream fis = new FileInputStream("F:/hello.txt");
//创建字节数组作为读取文件的容器
byte[] data = new byte[1024]; //创建1KB为单位的
//读取数据到程序中 读取1kb 返回值是int类型
int len = fis.read(data); //1次读1K 如果文件没有1K 用len变量记录实际读多少 单位:字节
System.out.println("实际读取了:" + len + "字节");
//把读取到的数据转换为字符串 从数组的第一位到data的长度为结束
String str = new String(data, 0, len);
System.out.println(str);
//关闭
fis.close();
}
}
(3)复制文件
/**
* @document: 文件复制
* @Author:SmallG
* @CreateTime:2023/8/3+10:17
*/
//1、把文件读取到内存中
//2、把内存中的数据写到新的文件中
public class Demo12 {
public static void main(String[] args) throws IOException {
//创建输入流 用于读取文件
FileInputStream fis = new FileInputStream("F:/hello.txt");
//创建输出流 用于写入文件
FileOutputStream fos = new FileOutputStream("D:/hello.txt");
//定义一个用于存放读取到的文件内容
byte[] data = new byte[1024]; //一次只读1k
//循环读取 因为文件的大小不确定 因此不知道要读取多少次
int len = -1;
while ((len = fis.read(data)) != -1) {
String str = new String(data, 0, len, "utf-8");
fos.write(data, 0, len); //读了多少写多少
}
//关闭数据流 关闭原则:后使用的先关闭
fos.close();
fis.close();
}
}
4、缓冲流
用于提高读写信息的效率,是一种处理流
(1)BIS与BOS
- BufferedInputStream和BufferedOutputStream称为字节缓存流
- BufferedInputStream:缓冲字节输入流内部维护着一个缓冲区,使用时该流会尽可能多的一次读取若干字节存入缓冲区,如何逐一但返回字节,直到缓冲区的数据全部读取完毕,然后再读取若干字节存入缓冲区,循环往复。
- BufferedOutputStream:缓冲输出流内部维护一个缓冲区(字节数组)。缓冲区满,数据会一次性全部写出。
/**
* @document: 缓冲流:提高文件读取的效率 降低频繁访问硬盘的速度
* 1、BufferedInputStream
* 2、BufferedOutputStream
* @Author:SmallG
* @CreateTime:2023/8/3+11:17
*/
public class Demo13 {
public static void main(String[] args) throws IOException {
//读取本地文件
FileInputStream fis = new FileInputStream("C:/Git-2.33.0.2-64-bit.exe");
//默认能缓冲8192字节
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("D:/new");
//默认8192字节
BufferedOutputStream bos = new BufferedOutputStream(fos);
int len = -1;
while ((len = bis.read()) != -1) {
bos.write(len);
}
bos.close(); //在关闭缓冲流时 默认会先调用flush()方法 清除缓存
bis.close();
fos.close();
fis.close();
}
}
(2)flush方法
- 输出缓冲流提供了flush方法:强制将当前缓冲区中缓存的字节一次性写出,随需随取。
- 输出缓冲流的close方法中默认也会调用一次flush方法,保证清空缓冲区。
5、序列化和反序列化
*对象序列化的条件:对象的类必须实现Serializable接口,同时类中的所有属性必须实现Serializable接口。
(1)对象序列化概念
- 对象存储于内存之中,当需要将对象保存在硬盘上或者传输到另一台计算机上时,会用到对象的序列化。
- 对象---------->字节序列(对象序列化)。
- 字节序列----->对象(对象的反序列化)。
(2)OIS与OOS
java中序列化和反序列化通过对象流实现
- ObjectOutputStream:将对象进行序列化输出流(对象-------->字节序列)
- ObjectInputStream:将对象进行反序列化的输入流(字节序列------->对象)
(3)Serializable接口
这个接口创建的对象就是可序列化的对象,(Java中的包装类和String类均实现了Serializable接口),该接口中的方法对程序是不可见的,因此实现接口的类不需要实现额外的方法,只是作为可序列化的标志。
(4)transient关键字
被transient关键字修饰的属性再序列化是其值将被忽略。忽略掉一些属性,不让他序列化。
Person类:
/**
* @document:完成序列化
* @Author:SmallG
* @CreateTime:2023/8/3+14:13
*/
public class Person implements Serializable {
//创建属性
private String name;
private int age;
private char gender;
private transient String[] otherInfo;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
", otherInfo=" + Arrays.toString(otherInfo) +
'}';
}
public Person(String name, int age, char gender, String[] otherInfo) {
this.name = name;
this.age = age;
this.gender = gender;
this.otherInfo = otherInfo;
}
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public String[] getOtherInfo() {
return otherInfo;
}
public void setOtherInfo(String[] otherInfo) {
this.otherInfo = otherInfo;
}
}
/**
* @document: 对象序列化
* @Author:SmallG
* @CreateTime:2023/8/3+14:19
*/
public class Demo14 {
public static void main(String[] args) throws IOException {
//首先创建一个对象
Person person = new Person("SmallG", 23, '男', new String[]{"黄色", "超帅", "帅锅"});
/*
* 1、先把对象通过对象输出流写入到文件中 ---->对象序列化
* 2、再把文件保存在硬盘中 这个过程称为数据持久化
* */
FileOutputStream fos = new FileOutputStream("F:/person.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
//写入文件
oos.writeObject(person);
oos.close();
fos.close();
}
}
反序列化:
/**
* @document: 反序列化
* @Author:SmallG
* @CreateTime:2023/8/3+14:30
*/
public class Demo15 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//首先指定文件
FileInputStream fis = new FileInputStream("F:/person.txt");
//创建对象输入流,读取对象内容
ObjectInputStream ois = new ObjectInputStream(fis);
//转化为对象 (需要强转)
Person person = (Person) ois.readObject();
System.out.println(person);
ois.close();
fis.close();
}
}
三、练习
1 读取文本文件中的每一行数据信息,链接后再存入最后一行
文本文件pw.txt的内容如下所示:
操作完毕之后的文本文件pw.txt的内容如下所示:
/**
* @document: 读取文本文件中的每一行数据信息,链接后再存入最后一行 字节流
* @Author:SmallG
* @CreateTime:2023/8/2+15:47
*/
public class ReadFile {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("src/day03/homework/pw.txt");
FileOutputStream fos = new FileOutputStream("src/day03/homework/pw.txt", true);
byte[] data = new byte[1024];
int len = -1;
StringBuilder builder = new StringBuilder();
while ((len = fis.read(data)) != -1) {
String str = new String(data, 0, len);
builder.append(str.replace("\n","").replace("\r",""));
}
byte[] s = builder.toString().getBytes();
fos.write(s);
fos.close();
fis.close();
}
}
2 实现简单的注册登录程序
编写一个程序,可以实现单个用户的简单注册和登录功能。
注册是保存用户输入的用户名和密码;登录是使用用户输入的用户名和密码与用户注册时的信息进行比对。
该程序的重点在于用户信息的持久化存储:之前编写的程序,数据都是临时存储在内存中,程序一旦关闭,用户输入的数据将被清除。本例需要实现的效果是当用户注册成功后,即使程序重启,也能够正常实现登录操作。
具体要求:
1、提供简单的用户交互菜单
2、用户注册时,利用序列化实现用户注册信息的持久化存储,存储位置为当前包,存储文件名为user.obj
3、用户登录时,利用反序列化实现用户注册信息的读取,和用户登录使用的用户名和密码进行比对,返回比对结果
4、仅需实现单个用户的注册和登录功能即可,也就是说新注册的用户会覆盖之前用户的注册信息
程序运行效果如下所示:
/**
* @document: 用户类
* @Author:SmallG
* @CreateTime:2023/8/3+18:19
*/
public class User implements Serializable {
String name;
String password;
public User() {
}
public User(String name, String password) {
this.name = name;
this.password = password;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
/**
* @document:
* @Author:SmallG
* @CreateTime:2023/8/3+18:26
*/
public class LoginSetup {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Scanner scanner = new Scanner(System.in);
boolean flag = true;
while (flag) {
System.out.println("---------主菜单---------");
System.out.println(" 请选择功能:");
System.out.println(" 1 注册");
System.out.println(" 2 登录");
System.out.println(" 3 退出");
System.out.println("请输入:");
int n = scanner.nextInt();
switch (n) {
case 1:
System.out.println("---------注册---------");
System.out.println("请输入用户名:");
String name = scanner.next();
System.out.println("请输入密码:");
String password = scanner.next();
System.out.println("请再次输入密码:");
String repassword = scanner.next();
if (!password.equals(repassword)) {
System.out.println("两次密码不一致,注册失败");
} else {
User user = new User(name, password);
setup(user);
System.out.println("注册成功");
}
break;
case 2:
System.out.println("---------登录---------");
System.out.println("请输入用户名:");
String name1 = scanner.next();
System.out.println("请输入密码");
String password1 = scanner.next();
boolean flag1 = login(name1, password1);
if (flag1) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
break;
case 3:
System.out.println("退出成功");
flag = false;
break;
default:
System.out.println("输入序号有误,请重新输入!");
break;
}
}
}
//登录方法
public static boolean login(String name, String password) throws IOException, ClassNotFoundException {
File file = new File("src/day03/homework/user.obj");
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
if (!file.exists()) {
return false;
}
User user = (User) ois.readObject();
if (user != null) {
if (name.equals(user.name) && password.equals(user.password)) {
return true;
} else {
return false;
}
}
return false;
}
//注册
public static void setup(User user) throws IOException {
try (
FileOutputStream fos = new FileOutputStream("src/day03/homework/user.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
) {
oos.writeObject(user);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
3 汉诺塔问题
据传开天辟地之神勃拉玛在一个庙里留下了三根金刚石的棒,第一根上面套着64个圆的金片,最大的一个在底下,其余一个比一个小,依次叠上去,庙里的众僧不倦地把它们一个个地从这根棒搬到另一根棒上,规定可利用中间的一根棒作为帮助,但每次只能搬一个,而且大的不能放在小的上面。就是这看似简单的问题,却困扰了人们千年以上。
后来,这个传说就演变为汉诺塔游戏,玩法如下:
1、有三根杆子A,B,C,A杆上有若干碟子
2、每次移动一块碟子,小的只能叠在大的上面
3、把所有碟子从A杆全部移到C杆上
你的任务是精确计算出到底需要移动多少次。很明显,如果只有2个圆盘,需要移动3次。圆盘数为3,则需要移动7次。如下图所示:
提示:基于递归的解决方案。
程序运行效果如下所示:
/**
* @document: 汉诺塔
* @Author:SmallG
* @CreateTime:2023/8/2+16:10
*/
public class Han {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入盘数:");
int n = scanner.nextInt();
move(n, 'A', 'B', 'C');
System.out.println("移动步数:" + count);
}
static int count = 0;
public static void move(int n, char A, char B, char C) {
if (n == 1) {
System.out.println("盘 " + n + " 由 " + A + " 移至 " + C);
} else {
move(n - 1, A, C, B);
System.out.println("盘 " + n + " 由 " + A + " 移至 " + C);
move(n - 1, B, A, C);
}
count++;
}
}