本文由 程序喵正在路上 原创,CSDN首发!
系列专栏:Java从入门到大牛
首发时间:2023年8月9日
欢迎关注点赞收藏留言
一以贯之的努力 不得懈怠的人生
double money = 9999.5; // 变量
int[] age = new int[100]; // 数组
Student s = new Student(); // 对象
List<Student> students = new ArrayList<>(); // 集合
上面这些都是内存中的数据容器,它们记住的数据,在断点或者程序终止时会丢失
有些数据想长久保存起来,该怎么办 ?
File
File 是 java.io 包下的类,File 类的对象,用于代表当前操作系统的文件(可以是文件或者文件夹)
注意:File 类只能对文件本身进行操作,不能读写文件里面存储的数据
IO流
用于读写数据的,可以读写文件或者网络中的数据…
绝对路径、相对路径
绝对路径:从盘符开始
File f1 = new File("D:\\soft\\QQ");
相对路径:不带盘符,默认直接到当前工程下的目录寻找文件
File f2 = new File("模块名\\a.txt");
具体应用
import java.io.File;
/**
* 目标:掌握File创建对象,代表具体文件的方案
*/
public class FileTest1 {
public static void main(String[] args) {
// 创建一个File对象,指代某个具体的文件
File f1 = new File("D:/resource/a.txt"); // 方式一:最常用
// File f1 = new File("D:\\resource\\ab.txt"); // 方式二:第一个反斜杠用来转义
// File f1 = new File("D:" + File.separator +"resource" + File.separator + "ab.txt"); // 方式三:File.separator表示分隔符 "\"
System.out.println(f1.length()); // 文件大小
// 创建一个File对象,指代某个文件夹
File f2 = new File("D:/resource");
System.out.println(f2.length()); // 文件夹的大小,不是计算其包括的所有文件的大小
// 注意:File对象可以指代一个不存在的文件路径
File f3 = new File("D:/resource/b.txt");
System.out.println(f3.length());
System.out.println(f3.exists()); // false
// 我现在要定位的文件是在模块中,应该怎么定位呢?
// 绝对路径:带盘符的,不推荐使用
// File f4 = new File("D:\\code\\javasepromax\\file-io-app\\src\\c.txt");
// 相对路径(重点):不带盘符,默认是直接去工程下寻找文件的
File f4 = new File("file-io-app\\src\\itheima.txt");
System.out.println(f4.length());
}
}
具体应用
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
/**
目标:掌握File提供的判断文件类型、获取文件信息功能
*/
public class FileTest2 {
public static void main(String[] args) throws UnsupportedEncodingException {
// 1.创建文件对象,指代某个文件
File f1 = new File("D:/resource/a.txt");
// 2、public boolean exists():判断当前文件对象,对应的文件路径是否存在,存在返回true
System.out.println("f1.exists: " + f1.exists());
// 3、public boolean isFile() : 判断当前文件对象指代的是否是文件,是文件返回true,反之
System.out.println("f1.isFile: " + f1.isFile());
// 4、public boolean isDirectory(): 判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之
System.out.println("f1.isDirectory: " + f1.isDirectory());
// 5.public String getName():获取文件的名称(包含后缀)
System.out.println("f1.getName: " + f1.getName());
// 6.public long length():获取文件的大小,返回字节个数
System.out.println("f1.length: " + f1.length());
// 7.public long lastModified():获取文件的最后修改时间
long time = f1.lastModified();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
System.out.println("f1.lastModified: " + sdf.format(time));
// 8.public String getPath():获取创建文件对象时,使用的路径
File f2 = new File("D:\\resource\\ab.txt");
File f3 = new File("file-io-app\\src\\itheima.txt");
System.out.println("f2.getPath: " + f2.getPath());
System.out.println("f3.getPath: " + f3.getPath());
// 9.public String getAbsolutePath():获取绝对路径
System.out.println("f2.getAbsolutePath: " + f2.getAbsolutePath());
System.out.println("f3.getAbsolutePath: " + f3.getAbsolutePath());
}
}
注意:delete 方法默认只能删除文件和空文件夹,删除后的文件不会进入回收站
具体应用
import java.io.File;
/**
* 目标:掌握File创建和删除文件相关的方法
*/
public class FileTest3 {
public static void main(String[] args) throws Exception {
// 1、public boolean createNewFile():创建一个新文件(文件内容为空),创建成功返回true,反之
File f1 = new File("D:/code/aa.txt");
System.out.println(f1.createNewFile());
// 2、public boolean mkdir():用于创建文件夹,注意:只能创建一级文件夹
File f2 = new File("D:/code/aaa");
System.out.println(f2.mkdir());
// 3、public boolean mkdirs():用于创建文件夹,注意:可以创建多级文件夹
File f3 = new File("D:/code/bbb/ccc/ddd/eee/fff/ggg");
System.out.println(f3.mkdirs());
// 3、public boolean delete():删除文件,或者空文件,注意:不能删除非空文件夹
System.out.println(f1.delete());
System.out.println(f2.delete());
File f4 = new File("D:/code");
System.out.println(f4.delete());
}
}
使用listFiles方法时的注意事项:
具体应用
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
/**
* 目标:掌握File提供的遍历文件夹的方法
*/
public class FileTest4 {
public static void main(String[] args) throws IOException {
File f1 = new File("D:/code/aaa");
f1.mkdir();
for (int i = 1; i < 4; i++) {
File f2 = new File("D:/code/aaa/a" + i + ".txt");
if(!f2.createNewFile()) System.out.println("Failed to createNewFile!");
}
// 1、public String[] list():获取当前目录下所有的 "一级文件名称"到一个字符串数组中去返回
String[] names = f1.list();
for (String name : names) {
System.out.println(name);
}
// 2、public File[] listFiles():(重点)获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
File[] files = f1.listFiles();
for (File file : files) {
System.out.println(file.getAbsolutePath());
}
File f3 = new File("D:/code/aaa");
File[] files1 = f3.listFiles();
System.out.println(Arrays.toString(files1));
}
}
什么是方法递归 ?
递归的形式
使用方法递归时需要注意的问题:
递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出错误
案例导学-计算 n 的阶乘
需求:计算 n 的阶乘,例如 5 的阶乘 = 1 * 2 * 3 * 4 * 5
分析
具体实现
/**
* 目标:掌握递归的应用,执行流程和算法思想。
*/
public class RecursionTest1 {
public static void main(String[] args) {
System.out.println("5的阶乘是:" + f(5));
}
public static int f(int n){
// 终结点
if(n == 1){
return 1;
}else {
return f(n - 1) * n;
}
}
}
递归算法三要素:
f(n) = f(n-1) * n
案例-猴子吃桃问题
猴子第一天摘下若干桃子,当即吃了一半,觉得好不过瘾,于是又多吃了一个;第二天又吃了前天剩余桃子数量的一半,觉得好不过瘾,于是又多吃了一个;以后每天都是吃前天剩余桃子数量的一半,觉得好不过瘾,又多吃了一个;等到第10天的时候发现桃子只有1个了。
需求:请问猴子第一天摘了多少个桃子?
分析:
整体来看,每一天都是做同一个事件,典型的规律化问题,考虑递归三要素:
f(n) = (f(n + 1) + 1) * 2
/*
猴子吃桃问题
*/
public class RecursionTest2 {
public static void main(String[] args) {
System.out.println("猴子第一天摘了" + f(1) + "个桃子");
// 验证结果
double num = f(1);
for (int i = 1; i < 11; i++) {
System.out.print("第" + i + "天一开始有" + num + "个桃子");
num = num / 2 - 1;
if (num >= 0) System.out.println(", 吃完还剩" + num + "个桃子");
}
}
public static double f(double n) {
if (n == 10) {
return 1;
} else {
return (f(n + 1) + 1) * 2;
}
}
}
/*
思路分析:
f(2) = f(1) / 2 - 1;
f(3) = f(2) / 2 - 1;
f(4) = f(3) / 2 - 1;
...
f(9) = f(8) / 2 - 1;
f(10) = f(9) / 2 - 1 = 1;
*/
案例:文件搜索
需求:从 D 盘中,搜索 “QQ.exe” 这个文件,找到后直接输出其位置
分析
代码实现
import java.io.File;
/**
* 目标:掌握文件搜索的实现
*/
public class RecursionTest3 {
public static void main(String[] args) throws Exception {
searchFile(new File("D:/") , "QQ.exe");
}
/**
* 去目录下搜索某个文件
* @param dir 目录
* @param fileName 要搜索的文件名称
*/
public static void searchFile(File dir, String fileName) throws Exception {
// 1、把非法的情况都拦截住
if(dir == null || !dir.exists() || dir.isFile()){
return; // 代表无法搜索
}
// 2、dir不是null,存在,一定是目录对象
// 获取当前目录下的全部一级文件对象
File[] files = dir.listFiles();
// 3、判断当前目录下是否存在一级文件对象,以及是否可以拿到一级文件对象
if(files != null && files.length > 0){
// 4、遍历全部一级文件对象。
for (File f : files) {
// 5、判断文件是否是文件,还是文件夹
if(f.isFile()){
// 是文件,判断这个文件名是否是我们要找的
if(f.getName().contains(fileName)){
System.out.println("找到了:" + f.getAbsolutePath());
// 拓展:启动QQ
Runtime runtime = Runtime.getRuntime();
runtime.exec(f.getAbsolutePath());
}
}else {
// 是文件夹,继续重复这个过程(递归)
searchFile(f, fileName);
}
}
}
}
}
需求
删除非空文件夹
分析
代码实现
import java.io.File;
/**
* 删除非空文件夹
*/
public class RecursionTest4 {
public static void main(String[] args) throws Exception {
createTestFile();
File dir = new File("D:/test007");
deleteDir(dir); // 可以先注释这一行,到 D盘查看非空文件夹是否创建成功
}
// 创建用来删除的非空文件夹
public static void createTestFile() throws Exception {
// 创建多级文件夹
File f1 = new File("D:/test007/aaa/bbb/ccc/ddd/eee");
if (!f1.mkdirs()) return;
// 创建一级文件夹
File f2 = new File("D:/test007/fff");
if (!f2.mkdir()) return;
// 再随便添加一些文件
for (int i = 1; i < 4; i++) {
File f = new File("D:/test007/t00" + i + ".txt");
if (!f.createNewFile()) return;
}
File f3 = new File("D:/test007/aaa/a.jpg");
if (!f3.createNewFile()) return;
System.out.println("非空文件夹创建成功!");
}
// 删除非空文件夹
public static void deleteDir(File dir) {
if (dir == null || !dir.exists()) {
return;
}
// 是一个文件
if (dir.isFile()) {
if(dir.delete()) System.out.println("文件" + dir.getName() + "删除成功!");
return;
}
// 是文件夹
File[] files = dir.listFiles(); // 尝试取出所有一级文件对象
// 取出一级文件对象失败
if (files == null) {
return;
}
// 取出成功,但是空文件夹
if (files.length == 0) {
if(dir.delete()) System.out.println("文件" + dir.getName() + "删除成功!");
return;
}
// 是非空文件夹
for (File file : files) {
if (file.isFile()) { // 是文件,直接删除
if (file.delete()) System.out.println("文件" + file.getName() + "删除成功!");
} else {
deleteDir(file); // 是文件夹,递归删除
}
}
// 最后还剩下一个空文件夹,记得删除
if(dir.delete()) System.out.println("文件" + dir.getName() + "删除成功!");
}
}
执行结果
需求
啤酒 2 元 1 瓶,4 个盖子可以换一瓶,2 个空瓶可以换一瓶,请问 10 元钱可以喝多少瓶酒,剩余多少空瓶和盖子
代码实现
public class RecursionTest5 {
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("剩余瓶子数: " + lastBottleNumber);
System.out.println("剩余盖子数: " + lastCoverNumber);
}
// 买酒
public static void buy(int money) {
// 有多少钱先买酒
int buyNumber = money / 2; // 可买酒数
totalNumber += buyNumber;
// 瓶子和盖子还可以换酒
int allBottleNumber = buyNumber + lastBottleNumber; // 当前总瓶子数
int allCoverNumber = buyNumber + lastCoverNumber; // 当前总盖子数
int allMoney = 0; // 记录下次买酒还有多少钱
// 瓶子换酒
if (allBottleNumber >= 2) {
allMoney += (allBottleNumber / 2) * 2;
}
lastBottleNumber = allBottleNumber % 2;
// 盖子换酒
if (allCoverNumber >= 4) {
allMoney += (allCoverNumber / 4) * 2;
}
lastCoverNumber = allCoverNumber % 4;
// 如果钱还够买酒, 继续买
if (allMoney >= 2) {
buy(allMoney);
}
}
}
执行结果
标准ASCII字符集
GBK字符集(汉字内码扩展规范,国标)
Unicode字符集(统一码,也叫万国码)
Unicode 是国际组织规定的,可以容纳世界上所有文字、符号的字符集
UTF-8字符集
是 Unicode 字符集的一种编码方案,采取可变长编码方案,共分为四个长度区:1 个字节、2 个字节、3 个字节、4 个字节
英文字符、数字等只占 1 个字节(兼容标准 ASCII 编码),汉字字符占用 3 个字节
注意:技术人员在开发时都应该使用 UTF-8 编码!
什么是编码 ?
把字符按照指定的字符集编码成字节
什么是解码 ?
把字节按照指定的字符集编码解码成字符
Java 代码完成对字符的编码
Java 代码完成对字符的解码
具体应用
import java.util.Arrays;
/**
* 目标:掌握如何使用Java代码完成对字符的编码和解码
*/
public class Test {
public static void main(String[] args) throws Exception {
// 1、编码
String data = "a我b";
byte[] bytes = data.getBytes(); // 默认是按照平台字符集(UTF-8)进行编码的。
System.out.println(Arrays.toString(bytes));
// 按照指定字符集进行编码
byte[] bytes1 = data.getBytes("GBK");
System.out.println(Arrays.toString(bytes1));
// 2、解码
String s1 = new String(bytes); // 按照平台默认编码(UTF-8)解码
System.out.println(s1);
String s2 = new String(bytes1, "GBK");
System.out.println(s2);
}
}
什么是IO流 ?
输入输出流,用来读写数据的
如何学习IO流 ?
IO流的分类
IO 流总体来看就有四大流:字节输入流、字节输出流、字符输入流、字符输出流
IO流的体系
FileInputStream(文件字节输入流)
作用:以内存为基准,可以把磁盘文件中的数据以字节的形式读入到内存中去
注意事项
使用 FileInputStream 每次读取一个字节,读取性能较差,并且读取汉字输出会乱码
具体应用
准备一个测试文本文件:
import java.io.*;
/**
* 目标:掌握文件字节输入流,每次读取一个字节
*/
public class FileInputStreamTest1 {
public static void main(String[] args) throws Exception {
// 1、创建文件字节输入流管道,与源文件接通
// InputStream is = new FileInputStream(new File("file-io-app/src/test1.txt"));
// 简化写法:推荐使用
InputStream is = new FileInputStream(("file-io-app/src/test1.txt"));
// 2、开始读取文件的字节数据
// public int read():每次读取一个字节返回,如果没有数据了,返回-1
int b; // 用于记住读取的字节
while ((b = is.read()) != -1){
System.out.print((char) b);
}
// 读取数据的性能很差!
// 读取汉字输出会乱码!!无法避免的!!
// 流使用完毕之后,必须关闭!释放系统资源!
is.close();
}
}
FileInputStream(文件字节输入流)
作用:以内存为基准,可以把磁盘文件中的数据以字节的形式读入到内存中去
注意事项
使用 FileInputStream 每次读取多个字节,读取性能得到了提升,但读取汉字输出还是会乱码
具体应用
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 目标:掌握使用FileInputStream每次读取多个字节
*/
public class FileInputStreamTest2 {
public static void main(String[] args) throws Exception {
// 1、创建一个字节输入流对象代表字节输入流管道与源文件接通
InputStream is = new FileInputStream("file-io-app/src/test1.txt");
// 2、开始读取文件中的字节数据:每次读取多个字节
// public int read(byte b[]) throws IOException
// 每次读取多个字节到字节数组中去,返回读取的字节数量,读取完毕会返回-1
byte[] buffer = new byte[3];
int len; // 记住每次读取了多少个字节
while ((len = is.read(buffer)) != -1){
// 注意:读取多少,倒出多少。
String rs = new String(buffer, 0 , len);
System.out.print(rs);
}
// 性能得到了明显的提升!!
// 但是这种方案也不能避免读取汉字输出乱码的问题!!
is.close(); // 关闭流
}
}
方式一:自己定义一个字节数组与被读取的文件大小一样大,然后使用该字节数组,一次读完文件的全部字节
方式二:Java 官方为 InputStream 提供了如下方法,可以直接把文件的全部字节读取到一个字节数组中返回
直接把文件数据全部读取到一个字节数组可以避免乱码,是否还存在问题 ?
读写文本内容更适合用字符流,字节流适合做数据的转移,如:文件复制等
具体应用
import java.io.*;
/**
* 目标:使用文件字节输入流一次读取完文件的全部字节
*/
public class FileInputStreamTest3 {
public static void main(String[] args) throws Exception {
// 1、一次性读取完文件的全部字节到一个字节数组中去
// 创建一个字节输入流管道与源文件接通
InputStream is1 = new FileInputStream("file-io-app/src/test2.txt");
// 2、方式一:准备一个字节数组,大小与文件的大小正好一样大
File f = new File("file-io-app/src/test2.txt");
long size = f.length();
byte[] buffer1 = new byte[(int) size];
int len = is1.read(buffer1);
System.out.println(new String(buffer1));
System.out.println("---------------------------------");
// 方式二:使用Java提供的方法
InputStream is2 = new FileInputStream("file-io-app/src/test2.txt");
byte[] buffer2 = is2.readAllBytes();
System.out.println(new String(buffer2));
// 关闭流
is1.close();
is2.close();
}
}
FileOutputStream(文件字节输出流)
作用:以内存为基准,把内存中的数据以字节的形式写出到文件中
具体应用
import java.io.*;
/**
* 目标:掌握文件字节输出流FileOutputStream的使用
*/
public class FileOutputStreamTest4 {
public static void main(String[] args) throws Exception {
// 1、创建一个字节输出流管道与目标文件接通
// 覆盖管道:覆盖之前的数据
// OutputStream os =
// new FileOutputStream("file-io-app/src/testOut.txt");
// 追加数据的管道
OutputStream os =
new FileOutputStream("file-io-app/src/testOut.txt", true);
// 2、开始写字节数据出去
os.write(97); // 97就是一个字节,代表a
os.write('b'); // 'b'也是一个字节
byte[] bytes = "我爱你中国abc".getBytes();
os.write(bytes);
os.write("\n".getBytes()); // 换行符
os.write(bytes, 0, 15); // 一个汉字占3个字节
os.close(); // 关闭流
}
}
字节流非常适合做一切文件的复制操作。任何文件的底层都是字节,字节流做复制,是一字不漏地转移完全部字节,只要复制后的文件格式一致就没问题
具体应用
import java.io.*;
/**
* 目标:使用字节流完成对文件的复制操作
*/
public class CopyTest5 {
public static void main(String[] args) throws Exception {
// 需求:复制照片
// 1、创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("file-io-app/src/1.png");
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("file-io-app/src/copy.png");
// 3、创建一个字节数组,负责转移字节数据
byte[] buffer = new byte[1024];
// 4、从字节输入流中读取字节数据,写出去到字节输出流中。读多少写出去多少
int len; // 记住每次读取了多少个字节
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}
os.close();
is.close();
System.out.println("复制完成!!");
}
}
try {
...
} catch(IOException e) {
e.printStackTrace();
} finally {
...
}
finally 代码区的特点:无论 try 中的程序是正常执行了,还是出现了异常,最后都一定会执行 finally 区,除非 JVM 终止
作用:一般用于在程序执行完成后进行资源的释放操作(专业级做法)
具体应用
/**
* 目标:认识try-catch-finally
*/
public class Test1 {
public static void main(String[] args) {
try {
System.out.println(10 / 2);
// return; // 跳出方法的执行
// System.exit(0); // 虚拟机
}catch (Exception e){
e.printStackTrace();
} finally {
System.out.println("===finally执行了一次===");
}
System.out.println(div(10, 2));
}
public static int div(int a, int b){
try {
return a / b;
}catch (Exception e){
e.printStackTrace();
return -1; // 代表的是出现异常
}finally {
// 千万不要在finally中返回数据!
return 111;
}
}
}
手动释放资源
/**
* 目标:掌握释放资源的方式
*/
public class Test2 {
public static void main(String[] args) {
InputStream is = null;
OutputStream os = null;
try {
// 1、创建一个字节输入流管道与源文件接通
is = new FileInputStream("file-io-app/src/1.png");
// 2、创建一个字节输出流管道与目标文件接通
os = new FileOutputStream("file-io-app/src/copy.png");
// 3、创建一个字节数组,负责转移字节数据
byte[] buffer = new byte[1024];
// 4、从字节输入流中读取字节数据,写出去到字节输出流中
int len; // 记住每次读取了多少个字节
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}
System.out.println("复制完成!!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 释放资源的操作
try {
if(os != null) os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(is != null) is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
JDK 7 开始提供了更简单的资源释放方案:try-with-resource
try(定义资源1; 定义资源2; ...) {
可能出现异常的代码;
} catch(异常类名 变量名) {
异常的处理代码;
}
上述资源使用完毕后,会自动调用其 close() 方法,完成对资源的释放
() 中只能放置资源,否则报错
什么是资源呢?
资源一般指的是最终实现了 AutoCloseable 接口
public abstract class InputStream implements Closeable {}
public abstract class OutputStream implements Closeable, Flushable {}
public interface Closeable extends AutoCloseable {}
具体应用
public class MyConnection implements AutoCloseable{
@Override
public void close() throws Exception {
System.out.println("释放了与某个硬件的链接资源~~~~");
}
}
import java.io.*;
/**
* 目标:掌握释放资源的方式:try-with-resource
*/
public class Test3 {
public static void main(String[] args) {
try (
// 注意:这里只能放置资源对象(流对象)
// 1、创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("file-io-app/src/1.png");
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("file-io-app/src/copy.png");
MyConnection conn = new MyConnection();
){
// 3、创建一个字节数组,负责转移字节数据
byte[] buffer = new byte[1024];
// 4、从字节输入流中读取字节数据,写出去到字节输出流中
int len; // 记住每次读取了多少个字节
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}
System.out.println("复制完成!!");
} catch (Exception e) {
e.printStackTrace();
}
}
}