Day09、File、方法递归、IO流
1 、先要定位文件
. File 类可以定位文件: 进行删除、获取文本本身信息等操作。
. 但是不能读写文件内容。
2 、读写文件数据
. IO流技术可以对硬盘中的文件进行读写
3 、今日总体学习思路
. 先学会使用File 类定位文件以及操作文件本身
. 然后学IO流读写文件数据。
![image-20220510151243208]()
File
File类概述
-
File 类在@java.io.File 下、代表操作系统的文件对象( 文件、文件夹) 。
-
File 类提供了诸如: 定位文件, 获取文件本身的信息、删除文件、创建文件( 文件夹) 等功能。
File类创建对象
| 方法名称 |
说明 |
| public File(String pathname) |
根据文件路径创建文件对象 |
| public File(String parent, String child) |
从父路径名字符串和子路径名字符串创建文件对象 |
| public FiIe(File parent, String child) |
根据父路径对应文件对象和子路径名字符串创建文件对象 |
-
File 对象可以定位文件和文件夹
-
File 封装的对象仅仅是一个路径名, 这个路径可以是存在的, 也可以是不存在的
绝对路径和相对路径
绝对路径:从盘符开始
File f1 = new File("E:\\wallpapercache\\img\\2.jpg");
相对路径:不带盘符,默认直接到当前工程下的目录寻找文件
File f2 = new File("src\\data.txt");
存在方法:
//exists()方法 判断这个路径是否存在,这个文件夹存在否
System.out.println(f3.exists());
File类的判断文件类型、获取文件信息功能
| 方法名称 |
说明 |
| public boolean isDirectory() |
测试此抽象路径名表示的File是否为文件夹 |
| public boolean isFile() |
测试此抽象路径名表示的File是否为文件 |
| public boolean exists() |
测试此抽象路径名表示的File是否存在 |
| public String getAbso;utePath() |
返回此抽象路径名的绝对路径名字符串 |
| public String getPath() |
将此抽象路径名转换为路径名字符串 |
| public String getName() |
返回由此抽象路径名表示的文件或文件夹的名称 |
| public long lastModified() |
返回文件最后修改的时间毫秒值 |
Fi1e 类创建文件的功能
| 方法名称 |
说明 |
| public boolean createNewFile() |
创建一个新的空的文件 |
| public boolean mkdir() |
只能创建一级文件夹 |
| public boolean mkdirs() |
可以创建多级文件夹 |
File 类删除文件的功能
| 方法名称 |
说明 |
| public boolean delete() |
删除由此抽象路径名表示的文件或空文件夹 |
-
delete 方法直接删除不走回收站; 如果删除的是一个文件, 且文件没有被占用则直接删除。
-
delete 方法默认只能删除空文件夹。
File类的遍历功能
| 方法名称 |
说明 |
| public String[] list() |
获取当前目录下所有的" 一级文件名称" 到一个字符串数组中去返回。 |
| public File[] listFiles()( 常用) |
获取当前目录下所有的" 一级文件对象" 到一个文件对象数组中去返回( 重点) |
listFiles 方法注意事项:
当调用者不存在时, 返回null
当调用者是一个文件时, 返回null
当调用者是一个空文件夹时, 返回一个长度为0 的数组
当调用者是一个有内容的文件夹时, 将里面所有文件和文件夹的路径放在File数组中返回
当调用者是一个有隐藏文件的文件夹时, 将里面所有文件和文件夹的路径放在File数组中返回, 包含隐藏内容
当调用者是一个需要权限才能进入的文件夹时,返回null
递归
什么是方法递归?
-
方法直接调用自己或者间接调用自己的形式称为方法递归( recursion) 。
-
递归做为一种篡法在程序设计语言中广泛应用。
递归的形式
-
直接递归: 方法自己调用自己。
-
间接递归: 方法调用其他方法, 其他方法又回调方法自己。
方法递归存在的问题?
-
递归如果没有控制好终止, 会出现递归死循环, 导致栈内存溢出现象。
递归解决问题的思路:
把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
递归算法三要素大体可以总结为·
-
递归的公式: f(n) =f(n-1) * n;
-
递归的终结点: f(1)
-
递归的方向必须走向终结点:
public class RecusionDemo02 {
public static void main(String[] args) {
int result = f(3);
System.out.println(result);
}
public static int f(int n) {
if (n == 1) {
return n;
} else {
return f(n - 1) * n;
}
}
}
案例 :计算1-n的和
-
公式: f(n) =1+2+3+4+...+(n-1)+n;
-
公式的等价形式: f(n) = f(n-1) + n;
-
递归的方向必须走向终结点:f(1) = 1;
public class RecusionDemo03 {
public static void main(String[] args) {
int result = f(5);
System.out.println(result);
}
public static int f(int n) {
if (n == 1) {
return n;
} else {
return f(n - 1) + n;
}
}
}
递归经典案例:猴子吃桃子问题
/**
* 猴子吃桃问题
* f(10) = 1;
* f(x) - f(x)/2 -1 = f(x+1)
* f(x) = 2f(x+1) + 2
* 求f(1)
* f(10) = 1;
*/
public class RecusionDemo04 {
public static void main(String[] args) {
int result = f(1);
System.out.println(result);
}
public static int f(int n) {
if (n == 10) {
return 1;
} else {
return 2 * f(n + 1) + 2;
}
}
}
1、在上述的案例中递归算法都是针对存在规律化的递归问题。
2、有很多问题是非规律化的递归问题, 比如文件搜索。如何解决?
-
非规律化递归问题自己看着办, 需要流程化的编程思维。
案例:文件搜索
需求, 文件搜索、从C : 盘中, 搜索出某个文件名称并输出绝对路径。
分析:
先定位出的应该是一级文件对象
遍历全部一级文件对象, 判断是否是文件
如果是文件, 判断是否是自己想要的
如果是文件夹, 需要继续递归进去重复上述过程
public class RecusionDemo05 {
public static void main(String[] args) {
//2.传入目录和要找的文件
//File f1 = new File("E:/");
searchFile(new File("E:/"), "Code.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().equals(fileName))
System.out.println("文件路径为:" + file.getAbsoluteFile());
//启动它
/*Runtime r = Runtime.getRuntime();
try {
r.exec(file.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}*/
} else {
//8.是文件夹 递归寻找
searchFile(file, fileName);
}
}
}
} else {
System.out.println("对不起,当前搜索的不是文件夹");
}
}
}
案例:啤酒问题
/**
* 啤酒2元一瓶 4个盖子换一瓶 2个空瓶换一瓶
* 请问十元能喝多少瓶酒 剩余多少空瓶和盖子
* 答案:15瓶 3个盖子 1个瓶子
*/
public class RecusionDemo06 {
//定义静态的成员变量用于存储可以买的酒数量
public static int totalNumber;//总数量
public static int lastBottleNumber;//剩余瓶子个数
public static int lastCoverNumber;//剩余盖子个数
public static void main(String[] args) {
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;
totalNumber += Buynumber;
//3.把盖子和瓶子换成钱
int coverNumber = lastCoverNumber + Buynumber;
int bottleNumber = lastBottleNumber + Buynumber;
//4.统计可以换的钱
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);
}
}
}
字符集基础知识:
![image-20220511105832594]()
ASCII 字符集:
ASCII 使用1 个字节存储一个字符, 一个字节是8 位, 总共可以表示1 28 个子符信息, 对于英文, 数字来说是够用的。
0110001 = 97 => a
01100010 = 98 => b
GBK:
-
window 系统默认的码表。兼容ASCII表, 也包含了几万个汉字, 并支持繁体汉字以及部分日韩文字。
-
注意: GBK 是中国的码表, 一个中文以两个字节(16位)的形式存储。但不包含世界上所有国家的文字。->2的16次
Unicode码表:
-
unicode ( 又称统一码、万国码、单一码) 是计算忄斗学领域里的一项业界字符编码标准。
-
容纳世界上大多数国家的所有常见文字和符号。
-
由于Unicode 会先通过UTF-8, UTF-16 , 以及UTF-2 的编码成二进制后再存储到计算机, 其中最为常见的就是UTF-8
注意
-
Unicode 是万国码, 以UTF-8 编码后一个中文一般以三个字节的形式存储。
-
UTF-8 也要兼容ASCII编码表。
-
技术人员都应该使用UTF-8 的字符集编码。
-
编码前和编码后的字符集需要一致, 否则会出现中文乱码。
汉字存储和展示过程解析
![image-20220511110752577]()
-
字符串常见的字符底层组成是什么样的?
-
英文和数字等在任何国家的字符集中都占1 个字节
-
GBK 字符中一个中文字符占2 个字节
-
UTF-8 编码中一个中文1 般占3 个字节
-
编码前的字符集和编码好的字符集有什么要求?
-
必须一致, 否则会出现中文字符乱码
-
英文和数字在任何国家的编码中都不会乱码
String 编码
| 方法名称 |
说明 |
| byte[] getBytes() |
使用平台的默认字符集将该String 编码为一系列字节, 将结果存储到新的字节数组中 |
| byte[] getBytes(String charsetName) |
使用指定的字符集将该String 编码为一系列字节, 将结果存储到新的字节数组中 |
String 解码
| 构造器 |
说明 |
| String(byte[] bytes) |
通过使用平台的默认字符集解码指定的字节数组来构造新的String |
| String(byte[] bytes, String charsetName) |
通过指定的字符集解码指定的字节数组来构造新的String |
IO流也成为输入、输出流,就是用来读写数据的
IO流概述
-
I表示intput,是数据从硬盘文件读入到内存的过程,称之输入,负责读
-
O表示output,是内存程序的数据从内存写出到硬盘文件的过程,称之输出,负责写
![image-20220511205601313]()
IO流的分类
![image-20220511210037955]()
总结流的四大类
-
字节输入流: 以内存为基准, 来自磁盘文件/ 网络中的数据以字节的形式读入到内存中去的流称为字节输入流。
-
字节输出流: 以内存为基准, 把内存中的数据以字节写出到磁盘文件或者网络中去的流称为字节输出流。
-
字符输入流: 以内存为基准, 来自磁盘文件/ 网络中的数据以字符的形式读入到内存中去的流称为字符输入流。
-
字符输出流: 以内存为基准, 把内存中的数据以字符写出到磁盘文件或者网络介质中去的流称为字符输出流。
![image-20220511210157903]()
![image-20220511210657403]()
-
作用:以内存位基准,把磁盘文件中的数据以字节的形式读取到内存中去。
| 构造器 |
说明 |
| public FilelnputStream(File file) |
创建字节输入流管道与源文件对象接通 |
| public FilelnputStream(String pathname) |
创建字节输入流管道与源文件路径接通 |
| 方法名称 |
说明 |
| public int read() |
每次读取一个字节返回, 如果字节已经没有可读的返回-1 |
| public int read(byte [ ] buffer) |
每次读取一个字节数组返回, 如果字节已经没有可读的返回-1 |
-
每次读取一个字节性能慢,读取中文字符输出无法编码乱码问题
byte[] buffer = new byte[3];
int len;//记录每次读取的字节数
while ((len = is.read(buffer)) != -1){
//都多少倒多少
System.out.print(new String(buffer,0,len));
}
-
每次读取一个字节数组:读取的性能得到提升,读取中文字符输出无法避免乱码问题
如何使用字节输入流读取中文内容输出不乱码呢?
-
定义一个与文件一样大的字节数组,一次性读取完文件的全部字节
直接把文件数据全部读取到一个字节数组可以避免乱码,是否存在问题
![image-20220511221242606]()
文件字节输出流:FileOutputStream
-
作用:以内存为基准,把内存中的数据以字节的形式写出到磁盘文件中去的流
| 构造器 |
说明 |
| public FiIeOutputStream(FiIe file) |
创建字节输出流管道与源文件对象接通 |
| public FileOutputStream(FiIe file, boolean append) |
创建字节输出流管道与源文件对象接通, 可追加数据 |
| public FileOutputStream(String filepath) |
创建字节输出流管道与源文件路径接通 |
| public FiIeOutputStream(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() |
关闭流, 释放资源, 但是在关闭之前会先刷新流。一旦关闭, 就不能再写数据 |
字节输出流如何实现数据追加
public Fi1eOutputStream(String filepath, boolean append)
os.write("\r\n".getBytes());//换行
//写数据必须刷新数据,才能是数据生效
//os.flush();刷新数据
os.close();//释放资源,包含了刷新的!关闭后流不可以使用
文件拷贝
![image-20220512153424989]()
public class CopyDemo01 {
public static void main(String[] args) {
try {
//1.创建一个文件字节输入流管道与源视频接通
InputStream is = new FileInputStream(new File("E:\\BaiduNetdiskDownload\\C Primer Plus公开课视频和代码" +
"\\C Primer Plus教学录屏\\C_Primer_Plus_5.7.mp4"));
//2.创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream(new File("E:\\course\\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 (IOException e) {
e.printStackTrace();
}
}
}
![image-20220512154800631]()
字节流适合做一切文件数据的拷贝吗?
-
任何文件的底层都是字节, 拷贝是一字不漏的转移字节, 只要前后文件格式、编码一致没有任何问题。
资源释放的方式
try-catch-finally:自动释放资源,代码简洁
-
finally 在异常处理时提供finally 块来执行所有清除操作, 比如说IO流中的释放资源
-
特点: 被finally 控制的语句最终一定会执行, 除非JVM退出
-
异常处理标准格式: try....catch...finally
try-catch-finally 格式
try {
FileOutputStream fos = new Fi1eOutputStream("a.txt");
fos.write(97);
fos.close();
} catch (IOException e) {
e.printStackTrace();
} finally{
}
//注意事项 就是抢不过System.exit(退出虚拟机)
public static int test(int a,int b){
try {
int c = a /b;
return c;
} catch (Exception e) {
e.printStackTrace();
return -1;//计算出现bug
} finally {
//哪怕上面有return语句执行,也必须先执行完这里才行
//开发中不建议在这加return,如果加了,返回的永远是这里的数据
return 100;
}
}
![image-20220512160613540]()
jdk7的方法
try (
//这里只能防止资源对象,用完会自动关闭,自动调用资源对象的close
//即使异常也会做出关闭操作
//1.创建一个文件字节输入流管道与源视频接通
InputStream is = new FileInputStream(new File("src\\data3.txt"));
//2.创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream(new File("src\\data4.txt"));
) {
//3.定义一个字节数组转移数据
byte[] buffer = new byte[1024];
int len;//记录每次读取的字节数
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
System.out.println("复制完成!");
System.out.println(10 / 0);
} catch (IOException e) {
e.printStackTrace();
}
}
注意
-
JDK 7 以及JDK 9 的0 中只能放置资源对象, 否则报错
-
什么是资源呢?
-
资源都是实现了Closeable/AutoCloseable 接口的类对象
public abstract class lnputStream implements CIoseable { }
public abstract class OutputStream implements CIoseable, FlushabIe{}
![image-20220512190602825]()
文件字符输入流: Reader
-
作用: 以内存为基准, 把磁盘文件中的数据以字符的形式读取到内存中去。
| 构造器 |
说明 |
| public FileReader(FiIe file) |
创建字符输入流管道与源文件对象接通 |
| public FiIeReader(String pathname) |
创建字符输入流管道与源文件路径接通 |
| 方法名称 |
说明 |
| public int read() |
每次读取一个字符返回, 如果字符已经没有可读的返回-1 |
| public int read(char[] buffer) |
每次读取一个字符数组, 返回读取的字符个数, 如果字符已经没有可读的返回-1 |
-
字符流读取中文字符不会出现乱码(如果代码和文件编码一致),但是性能较慢
-
每次读取一个字符数组能够让读取的性能得到提升。
文件字符输出流:FileWriter
作用: 以内存为基准, 把内存中的数据以字符的形式写出到磁盘文件中去的流。
| 构造器 |
说明 |
| public FiIeWriter(FiIe file) |
创建字符输出流管道与源文件对象接通 |
| public FiIeWriter(FiIe file, boolean append) |
创建字符输出流管道与源文件对象接通, 可追加数据 |
| public FileWriter(String filepath) |
创建字符输出流管道与源文件路径接通 |
| public FiIeWriter(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) |
写一个字符 |