前面我们所有的数据存储在内存中,程序一旦重启或者电脑关机后数据就丢失了,无法长久保存。因此实际工作中我们需要将用户数据全都保存到硬盘上,用以永久保存。
一、File
硬盘中的数据形式就是文件,文件是数据的载体
1.1 概述
- File类在包java.io.File下,代表操作系统的文件对象(文件,文件夹)。
- File类提供的功能:定位文件、获取文件本身信息、删除文件、创建文件(文件夹)等。
注意:File创建对象定位文件,删除,获取文件,但不能读写文件内容。
//1.File类创建对象,参数:(文件、文件夹、绝对路径、相对路径)
//路径中的\\转义为\,或者路径中使用/,功能一样
//还可使用File的API separator自动识别系统的间隔符,灵活性更高
File file1 = new File("D:\\code\\A.txt");
File file2 = new File("D:/code/A.txt");
File file3 = new File("D:" + File.separator + "code" + File.separator + "A.txt");
//File只是封装了一个路径,该路径可以是存在的也可不存在
File file4 = new File("A:/");
//当File参数为相对路径时,默认直接在当前工程目录下查找文件
File file5 = new File("A.txt");
//该方法用以返回绝对路径,D:\IDEA_Projects\A.txt
System.out.println(file5.getAbsolutePath());
//查看文件字节大小,可知文件创建成功
System.out.println(file3.length());
System.out.println(file4.length());
1.2 常用API
1.2.1 判断文件类型、获取文件信息
方法名称 | 说明 |
---|---|
public boolean isDirectory() | 测试此抽象路径名表示的File是否为文件夹 |
public boolean isFile() | 测试此抽象路径名表示的File是否为文件 |
public boolean exists() | 测试此抽象路径名表示的File是否存在 |
public String getAbsolutePath() | 返回此抽象路径名的绝对路径名字符串 |
public String getPath() | 返回File中包装的路径名字字符串 |
public String getName() | 返回由此抽象路径名表示的文件或文件夹的名称 |
public long lastModified | 返回文件最后修改时间毫秒值 |
1.2.2 创建文件、删除文件
delete方法默认只能删除文件和空文件夹,且删除后不走回收站。
方法名称 | 说明 |
---|---|
public boolean createNewFile() | 创建一个新的文件 |
public boolean mkdir() | 只能创建一级目录 |
public boolean mkdirs() | 创建多级目录 |
public boolean delete() | 删除由此抽象路径名表示的文件或空文件夹 |
1.2.3 遍历文件夹
- 当调用者不存在时,返回null
- 当调用者是一个文件时,返回null
- 当调用者是一个空文件夹时,返回长度为0的数组
- 当调用者是有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回
- 当调用者是一个隐藏文件的文件夹时,将里面所有文件和文件夹的路径放到File数组中返回,包含隐藏内容
- 当调用者是一个需要权限才能进入的文件夹时,返回null
方法名称 | 说明 |
---|---|
public String[] list() | 获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回 |
public File[] listFiles() | 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回。(重要) |
1.2.4 API练习
-
统计一个文件夹中每种文件的个数并打印
打印格式为:
txt:3个
doc:4个
jpg:6个
public static void main(String[] args) { //创建一个map对象 Map<String, Integer> map = new HashMap(); //创建一个路径变量 String path = "D:" + File.separator + "code"; //使用getFiles方法,传入路径和map map = getFiles(path, map); //判断map是否为空(设置的路径为文件就是null),大小是否大于0(文件夹中没有文件则大小为0) if (map != null && map.size() > 0) { //遍历 map.forEach((k, v) -> System.out.println(k + "-->" + v)); }else{ System.out.println("没有文件!!!"); } } public static Map getFiles(String path, Map<String, Integer> map) { //将传入路径创建为File对象 File file = new File(path); //判断File对象是否为目录,不是目录就直接返回null的map if (file.isDirectory()) { //遍历File对象中的文件对象数组 File[] fileArr = file.listFiles(); //遍历文件对象数组 for (File file1 : fileArr) { //判断是否为目录 if (!file1.isDirectory()) { //分割出文件的后缀 String[] fileSplit = file1.getName().split("\\."); String s = fileSplit[fileSplit.length - 1]; //看map中是否存在后缀 if (map.containsKey(s)) { //存在就继续值+1 int count = map.get(s) + 1; map.put(s, count); } else { //不存在,就将后缀存入,值置为1 map.put(s, 1); } //如果是目录就继续往里面去获取文件对象遍历 } else { getFiles(file1.getAbsolutePath(), map); } } } //返回map对象 return map; }
-
将某文件夹下一级文件对象,按照最近修改时间降序展示,并显示修改时间。
打印格式为:
aaa.txt:2021-03-22 10:23:23
dlei.doc:2021-03-21 08:23:23
meinv.jpg:2008-11-11 11:11:11
public static void main(String[] args) {
//文件路径
String path = "D:\\code";
//使用我们建的方法
List<File> list = getFileSort(path);
//遍历list对象
for (File file : list) {
//格式化最后修改时间为想要的格式,因为默认是毫秒数
String time = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(file.lastModified());
//打印文件名和时间
System.out.println(file.getName() + " : " + time);
}
}
//文件排序
public static List<File> getFileSort(String path) {
//创建ArrayList对象,并且调用创建的getFiles方法
List<File> list = getFiles(path, new ArrayList<File>());
//null(不是文件夹) size>0(list对象中有文件对象)
if (list != null && list.size() > 0) {
//比较文件对象的最后修改时间,并且排序
// Collections.sort(list, new Comparator<File>() {
// public int compare(File file, File newFile) {
// if (file.lastModified() < newFile.lastModified()) {
// return 1;
// } else if (file.lastModified() == newFile.lastModified()) {
// return 0;
// } else {
// return -1;
// }
//
// }
// });
Collections.sort(list, (o1,o2) -> o1.lastModified() < o2.lastModified() ? 1 : o1.lastModified() == o2.lastModified() ? 0 : -1);
}
//返回排序好的list对象
return list;
}
//获取所有文件对象,并存储到List中
public static List<File> getFiles(String realpath, List<File> files) {
//创建文件对象
File realFile = new File(realpath);
//判断文件是否为文件夹,如果不是就返回null的List
if (realFile.isDirectory()) {
//如果是文件夹,就遍历文件夹,获取该文件下的所有文件对象
File[] subfiles = realFile.listFiles();
//遍历文件对象数组
for (File file : subfiles) {
//判断文件对象是否为文件夹
if (file.isDirectory()) {
//如果是文件夹,就继续获取文件夹中的所有文件对象
getFiles(file.getAbsolutePath(), files);
} else {
//如果不是文件夹,就代表是文件,就将该文件对象存储到List中
files.add(file);
}
}
}
return files;
}
二、方法递归
2.1 概念
什么是递归?
- 当方法直接调用自己或者间接调用自己的形式称为方法递归(recursion);
- 直接调用自己:方法自己调用自己
- 间接调用自己:方法调用其他方法,其他方法又回调方法自己。
- 递归是算法的一种,在程序设计语言中广泛应用。
**递归的缺点:**递归如果没有控制好终止,会出现递归死循环,导致栈溢出现象。
2.2 核心要素
递归就是把一个复杂的问题,层层转为一个与原问题相似的规模较小的问题来求解。
递归算法三要素(必须有三要素才能写递归):
- 递归公式:先找到问题的关系式,例如阶乘的关系式就是 f(n)=f(n-1)*n,求和的关系式就是f(n)=f(n-1)+n
- 递归的终结点:找到问题的终点在哪里,例如阶乘的终点就是1,因为f(1)=1,求和的终点也是1
- 递归的方向:必须是走向终结点的,例如求和的关系式就是 f(n)=f(n-1)+n,终点为f(1)=1,该关系式可以求1-100的总和,因为n=100想要到终点需要一直减,所以公式是n和n-1的关系(100-1需要一个个减);但是不能求(1-(-100)的总和,因为n=-100想要到终点需要一直加,所以公式为f(n)=f(n+1)+n,是n和n+1的关系
//1-任何正数的总和
public static int sum(int n) {
if (n==1){
return 1;
}
return sum(n-1) + n;
}
//任何负数-1的总和
public static int sum(int n) {
if (n==1){
return 1;
}
return sum(n+1) + n;
}
2.3 经典案例
/*
* 猴子吃桃,猴子摘了若干桃子,第一天吃了桃子的一半后,又多吃一个,
* 第二天吃了前一天桃子的一半,又多吃一个,天天都吃前一天的一半后,多吃一个,
* 第十天桃子只剩下1个,问猴子一共摘了多少个桃子
* 分析:
* 每一天吃的是前一天的一半+1后,多吃一个,设天数为x,今天的桃子数为f(x),那么今天的f(x)和明天的f(x+1)的关系为
* f(x) / 2 - 1 = f(x+1)
* f(x) = 2f(x+1) + 2
* 那么关系公式就是:f(x) = 2f(x+1) + 2
* 当x=10时,f(10)=1;(递归终结点)
* 第一天是x,第二天是x+1,那么第十天可以从第一天往上加出来,所以递归方向正确
* 注意:若递归方向不正确时,
* 例如当f(x) = 2f(x+1) + 2方向不正确时,
* 公式可以改为f(x-1) = 2f(x) +2,得出f(x)和f(x-1)的关系为:f(x)=f(x-1)/2-1
* */
public static void main(String[] args) {
//求第一天的桃子数量
System.out.println(feibo(1));
//第二天的桃子数量
System.out.println(feibo(2));
}
public static int feibo(int n) {
//终结点为第十天
if (n == 10) {
return 1;
}
return 2 * feibo(n + 1) + 2;
}
2.4 非规律化递归
2.4.1 案例一:文件搜索
需求:文件搜索,从C:盘中,搜索出某个文件名称并输出绝对路径
public static void main(String[] args) {
//需求:文件搜索,从C:盘中,搜索出某个文件名称并输出绝对路径
getFilePath(new File("D:/"), "用户.txt");
}
public static void getFilePath(File dir, String fileName) {
//判断是否为null且是否为目录
if ( dir != null && dir.isDirectory()) {
//获取目录下所有一级文件对象数组
File[] fileArr = dir.listFiles();
//判断数组对象是否为null且长度大于0
if (fileArr != null && fileArr.length > 0) {
//遍历一级文件对象数组
for (File file : fileArr) {
//判断文件对象是否为文件还是目录
if (file.isFile()) {
if (file.getName().contains(fileName)) {
System.out.println("文件位置为:" + file.getPath());
}
} else {
//目录则继续查找
getFilePath(file, fileName);
}
}
}
} else {
System.out.println("搜索位置不是目录!!!");
}
}
2.4.2 案例二:啤酒问题
需求:啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,请问10元钱可以喝多少瓶啤酒,剩余多少个瓶子和盖子?
//一共买了多少瓶,和上次买后剩余的瓶子和盖子
public static int AllBottle;
public static int lastBottle;
public static int lastCover;
//需求:啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,请问10元钱可以喝多少瓶啤酒,剩余多少个瓶子和盖子?
public static void main(String[] args) {
buy(10);
System.out.println("总共买了:" + AllBottle);
System.out.println("剩余瓶子:" + lastBottle);
System.out.println("剩余盖子:" + lastCover);
}
public static void buy(int money) {
//钱一共能买多少瓶
int buyMoney = money / 2;
AllBottle += buyMoney;
//上次买后剩余的瓶子和盖子 + 这次买酒剩余的瓶子和盖子 统计
int bottle = buyMoney + lastBottle;
int cover = buyMoney + lastCover;
//因为2块一瓶酒,所以第一次买完后剩余钱就是0元了,所以初值为0,后面拿瓶子和盖子是换酒,当瓶子和盖子不够换时,并不能拿去换成钱
//统计盖子和瓶子能换多少瓶酒(瓶数*2就是换酒的钱,盖子*4就是换酒的钱)
int allmoney = 0;
//可以添加判断瓶子和盖子够不够数,然后变成下一次换酒的钱
if (bottle >= 2) {
allmoney += bottle / 2 * 2;
}
lastBottle = bottle % 2;
if (cover >= 4) {
allmoney += cover / 4 * 2;
}
lastCover = cover % 4;
//判断换酒的钱够不够换,不够换就不递归了
//这里也是递归的终结点
if (allmoney>=2){
buy(allmoney);
}
}
2.5 扩展
2.5.1 删除非空文件夹
三、字符集
3.1 概述
什么是字符集?
- 计算机底层中不可以直存储字符,底层只能存储二进制(0,1)
- 二级制可以转换为十进制
结论:计算机底层可以直接表示十进制的编码,计算机可一个给人类字符进行编号存储,这套编号规则就是字符集。
字符集有哪些?
ASCll字符集
- ASCll:包括数字、英文、符号。
- ASCll采用一个字节存储一个字符,一个字节是8位,总共可以表示128个字符信息,可以表示数字和英文。
GBK
- 因为ASCll字符集只能表示128个字符信息,并不能表示汉字,所以中国推出GBK编码,其中英文和数字与ASCll字符集一致
- windows系统默认的码表,兼容ASCll码表,也包含几万个汉字,并支持繁体汉字和部分日韩文字。
- 注意:GBK是是中国的码表,一个中文以两个字节存储。但不包含世界上所有国家文字。
Unicode码表
unicode(又称统一码、万国码、单一码)是计算机领域的一项业界字符编码标准。
- 容纳世界上大多数国家所有常见的文字和符号,一个中文一般以三个字节的形式存储
- Unicode会先通过UTF-8,UTF-16以及UTF-32的编码成二进制后在存入计算机,其中最常见是UTF-8
注意:
- Unicode是万国码,以UTF-8编码成一个中文,一般以三个字节的形式存储。
- UTF-8也兼容ASCLL编码表
- 技术人员都应该使用UTF-8字符编码
- 编码前和编码后的字符集必须一致,否则会出现中文乱码
- 英文和数字再任何国家的编码中都不会出现乱码(因为都兼容Ascall码表)
3.2 编码与解码
注意:编码和解码时的字符集必须一致。
编码 byte[] getBytes() 使用平台默认的编码字符集将该String编码为一系列字节,将结果存储到新的字节数组中 byte[] getBytes(String ,charseName) 使用指定的编码字符集将该String编码为一系列字节,将结果存储到新的字节数组中 解码 String(byte[] bytes) 使用平台默认的字符集解码指定的字节数组来构造新的String String(byte[] bytes,String charsetName) 使用指定的字符集解码指定的字节数组来构造新的String
public static void main(String[] args) throws UnsupportedEncodingException {
//因为UTF-8文字是三个字节,所以占用21字节
//因为GBK文字是两个字节,所以占用16字节
String str = "abc123我爱你中国";
//getBytes()默认编码格式为UTF-8,想编码的格式写入括号
byte[] bytes_UTF = str.getBytes();
//选择编码格式需要处理异常,因为程序怕你编码格式写错(此处我们选择抛出异常)
byte[] bytes_GBK = str.getBytes("GBK");
System.out.println(bytes_UTF.length);//18
//编码后:[97, 98, 99, 49, 50, 51, -26, -120, -111, -25, -120, -79, -28, -67, -96, -28, -72, -83, -27, -101, -67]
System.out.println(Arrays.toString(bytes_UTF));
System.out.println(bytes_GBK.length);//16
//编码后:[97, 98, 99, 49, 50, 51, -50, -46, -80, -82, -60, -29, -42, -48, -71, -6]
System.out.println(Arrays.toString(bytes_GBK));
//解码,注意:解码的格式必须与编码时的格式一致,不然会乱码
String strOpenUTF = new String(bytes_UTF,"UTF-8");
String strOpenGBK = new String(bytes_GBK,"GBK");
System.out.println(strOpenUTF);
System.out.println(strOpenGBK);
//乱码:abc123鎴戠埍浣犱腑鍥�
String strOpenError= new String(bytes_UTF,"GBK");
System.out.println(strOpenError);
}
四、IO流
4.1 概述
IO流也称为输入输出流,用来读写数据的。
- I表示input,是数据从硬盘读入内存的过程,称之为输入,负责读
- O表示output,是数据从内存写出到硬盘的过程,称之为输出,负责写
字节流和字符流如何选择?
- 字节流适合做一切文件数据的拷贝(音视频、文本)
- 字节流不适合读取中文内容输出
- 字符流适合做文本文件的操作(读、写)
4.2 IO流体系
流分为四大类:
- 字节输入流:以内存为基准,本地磁盘文件/网络中的数据以字节的形式读入到内存中的流称为字节输入流
- 字节输出流:以内存为基准,将内存中的数据以字节的形式写出到硬盘文件/网络中的流称为字节输出流
- 字符输入流:以内存为基准,本地磁盘的文件/网络中的数据以字符的形式读入到内存中的流称为字符输入流
- 字符输出流:以内存为基准,将内存中的数据以字符的形式写出到硬盘的文件/网络中的流称为字符输出流
4.2.1 文件字节输入流
作用:以内存为基准,本地磁盘文件/网络中的数据以字节的形式读入到内存中的流称为字节输入流
构造器 | 说明 |
---|---|
public FileInputStrem(File file) | 创建字节输入流管道与源文件对象接通 |
pubic FileInputStream(String pathName) | 创建字节输入流管道源文件路径接通 |
方法名称 | 说明 |
public int read() | 每次读取一个字节返回,如果无字节可读就返回-1 |
public int read(byte[] buffer) | 每次读取一个字节数组,返回读取的字节个数,如果无字节可读返回-1 |
public static void main(String[] args) throws IOException {
//这里我们使用多态,用父类引用指向子类对象
//创建字节输入流与源文件对象接通
// InputStream inFile = new FileInputStream(new File( "Logback-use" + File.separator+ "src" + File.separator + "demo1.txt"));
//创建字节输入流与文件路径接通,其实底层还是将路径new成文件对象(需要处理异常,因为系统怕你路径输入错误,这里我们抛出)
InputStream in = new FileInputStream("Logback-use" + File.separator+ "src" + File.separator + "demo1.txt");
//read每次读取一个字节,返回字节的编码值(int),没有可读时返回-1
// int len = in.read();
// System.out.println(len + "," + (char)len);//97
//当读取到一个文字时,会产生乱码,因为utf-8中一个文字占3个字节
// int len = in.read();
// System.out.println(len + "," + (char)len);//230,æ
//读取一个字节数组(设置每次读取的字节大小)
// byte[] buffer = new byte[3];
// int len = in.read(buffer);//返回值为读取的字节数,此时读取的字节存储在数组中,文字一般为负数
// System.out.println(len + "," + Arrays.toString(buffer));//3,[-26, -120, -111](这三个字节表示一个字)
//循环读取文件内容(一次读取一个字节数组)
byte[] buffer = new byte[3];
int len;
while ((len = in.read(buffer))!= -1){
//这里我们使用String的构造方法,打印显示如果不设置显示的字节长度就会出现乱码
//文件内容为abcd
//因为“abcd”是4个字节,一次读取3个字节存储到数组,当读第二次时只有1个字节可读
//那么字节数组中就只会覆盖第一个字节的内容,第一次存储的后两个字节内容并没有覆盖
//所以会导致打印abcdbc,所以我们设置每次打印显示(0-读取的字节数)来限制
// System.out.print(new String(buffer));//abcdbc
System.out.print(new String(buffer,0,len));//abcd
}
}
4.2.1.1 弊端的解决
弊端::读取中文字符输出(打印)无法避免乱码问题(读取一个字节和读取一个字节数组都会遇到这种问题)
处理方法:
- 方法一:我们可以创建一个字节数组,大小为源文件总字节大小,那样就可以一次性读完全部字节。
- 方法二:使用提供的API,可以读取全部字节(readAllBytes(),返回类型为byte[])
**问题:**如果文件过大,字节数组可能引起内存溢出
public static void main(String[] args) throws Exception {
//创建流和源文件对象的连接
File file1 = new File("Logback-use/src/demo1.txt");
FileInputStream in1 = new FileInputStream(file1);
//因为字节流只能使用一次,一旦读取了就无法在读了
File file2 = new File("Logback-use/src/demo1.txt");
FileInputStream in2 = new FileInputStream(file2);
//创建字节数组,长度为文件对象的总长度
byte[] buffer = new byte[(int) file1.length()];
//因为是一次性读取总字节长度,所以只需要read一下
in1.read(buffer);
//打印读取后的字节数组,使用String的构造方法,传入字节数组
System.out.print(new String(buffer));
//使用所提供的的API,readAllBytes(),使用String的构造方法
System.out.print(new String(in2.readAllBytes()));
//abcd我爱中国
//abcd我爱中国
//abcd我爱中国
//abcd我爱中国
}
4.2.2 文件字节输出流
字节流适合做一切文件数据的拷贝,因为任何文件底层都是字节,拷贝是一字不漏转移字节,只要前后文件格式,编码规则没有问题
作用:以内存为基准,将内存中的数据以字节的形式写出到硬盘文件/网络中的流称为字节输出流
注意:
- 字节输出流只能以字节为参数,默认字节输出流是覆盖的形式写数据
- 字节输出流使用write(“\r\n”.getBytes())来书写换行
- 输出流的地址必须加上文件名才能写,否则"(拒绝访问。)"
- 写出去的数据只有刷新数据后才能生效
- 一旦接通了流资源,使用完后就必须关闭,不然会浪费资源
构造器 | 说明 |
---|---|
public FileOutputStream(File file) | 创建字节输出流管道与源文件对象接通 |
public FileOutputStream(File file,boolean append) | 创建字节输出流管道与源文件接通,可追加数据 |
public FileOutputStream(String pathname) | 创建字节输出流管道与文件路径接通 |
public FileOutputStream(String pathName,boolean append) | 创建字节输出流管道与文件路径接通,可追加数据 |
方法名称 | 说明 |
public void write(int a) | 写一个字节出去 |
public void write(byte[] buffer) | 写一个字节数组出去 |
public void write(byte[] buffer,int pos,int len) | 写一个字节数组的一部分出去 |
流的关闭和刷新 | |
flush() | 刷新流,流还可以继续用 |
close() | 关闭流,释放资源,但是关闭前会先自动刷新流。一旦关闭,就不能写数据了 |
public static void main(String[] args) throws Exception {
// OutputStream out = new FileOutputStream(new File("Logback-use/src/demo1.txt"));
OutputStream out = new FileOutputStream("Logback-use/src/demo1.txt");
//write写数据,可以传入一个字节或字节数组
out.write(67);
out.write('B');
//刷新
out.flush();
byte[] buffer = {'a','b','c'};
out.write(buffer);
out.write(buffer,1,2);
//CBabcbc
out.close();
// out.write('1');//流已经关闭,在写会报错
//多加一个true参数,表示追加
OutputStream out1 = new FileOutputStream("Logback-use/src/demo1.txt",true);
//换行,需要使用getBytes()方法去将字符串变成字符数组
out1.write("\r\n".getBytes());
//getBytes()方法去将字符串变成字符数组,源文件的编码值必须与转变的编码值相同,不然会乱码,getBytes()默认UTF-8编码
byte[] bytes = "这是追加的数据".getBytes("Utf-8");
out1.write(bytes);
//CBabcbc
//这是追加的数据
}
4.2.2.1 字符串转字节数组
getBytes可以将字符串转为字节数组
"这是追加的数据".getBytes("Utf-8")
4.2.3 文件字符输入流
字节流读取中文输出会存在什么问题?
- 会乱码,或者内存溢出
读取中文输出,哪个流更合适?为什么?
- 字符流更合适,最小单位是按照单个字符读取的
构造器 | 说明 |
---|---|
public FileReader(File file) | 创建字符输入流管道与源文件对象接通 |
public FileReader(String pathname) | 创建字符输入流管道与源文件路径接通 |
方法名称 | 说明 |
public int read() | 每次读取一个字符返回,当无字符可读就返回-1 |
public int read(char[] buffer) | 每次读取一个字符数组,返回读取的字符个数,当无字符可读就返回-1 |
try (
// Reader redaer = new FileReader(new File("Logback-use\\src\\demo1.txt"));
Reader reader = new FileReader("Logback-use\\src\\demo1.txt");
) {
//每次读取一个字符
// int len = reader.read();
// System.out.println(len + "," + (char)len);//97,a
// int len1 = reader.read();
// System.out.println(len1 + "," + (char)len1);//25105,我
//创建一个字符数组,每次读取一个字符数组
char[] buffer = new char[3];
//返回值位读取了多少个字符个数
int len = reader.read(buffer);
System.out.println(len + "," + Arrays.toString(buffer));//3,[a, 我, 爱]
} catch (Exception e) {
e.printStackTrace();
}
4.2.4 文件字符输出流
- 输出流的地址必须加上文件名才能写,否则"(拒绝访问。)"
构造器 | 说明 |
---|---|
public FileWriter(File file) | 创建字符输出流管道与源文件对象接通 |
public FileWriter(File file,boolean append) | 创建字符输出流管道与源文件对象接通,可追加数据 |
public FileWriter(String filepath) | 创建字符输出流管道与源文件路径接通 |
public FileWriter(String filepath,boolean append) | 创建字符输出流管道与源文件路径接通,可追加数据 |
方法名称 | 说明 |
void write(int c) | 写一个字符 |
void write(char[] buffer) | 写一个字符数组 |
void write(char[] buffer,int off,int len) | 写一个字符数组的部分 |
void write(String str) | 写一个字符串 |
void write(String str,int off,int len) | 写一个字符串的一部分 |
关闭和刷新 | |
flush() | 刷新流,还可以继续写数据 |
close() | 关闭流,释放资源,关闭之前会自动刷新流,关闭后无法使用流 |
try (
Writer writer = new FileWriter("Logback-use/src/demo1.txt");
){
//写一个字符
writer.write(97);
//字符串为参数写入
writer.write("我是地球人");
writer.write("\r\n");
//字符数组为参数写入
char[] buffer = "我是中国人".toCharArray();
writer.write(buffer);
writer.write("\r\n");
writer.write(buffer,0,3);
//a我是地球人
//我是中国人
//我是中
} catch (IOException e) {
e.printStackTrace();
}
4.3 资源释放的方式
因为使用了IO流资源完毕后,我们还需要关闭他,防止过多的资源浪费。所以java提供了一些方式方便我们去提醒我们关闭资源。
注意:
- JDK7和JDK9的()中只能放置资源对象,否则报错
- 什么是资源?
- 资源就是实现了Closeable/AutoCloseable接口的类的对象
4.3.1 try-catch-finally
finally:在异常处理时提供finally块来执行所有清除操作,比如IO流中的释放资源
- 被finally控制的语句最终一定会执行,除非JVM退出
- 异常处理标准格式为:try…catch…finally
//标准使用格式
public static void main(String[] args) throws IOException {
OutputStream out = null;
try {
// int a = 10 / 0;//如果在out赋值前出现异常,那么out就是null
out = new FileOutputStream("a.txt");
out.write('a');
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("这里的一定会被执行,除非JVM退出");
//这里我们可以添加一个判断,看out流是否为null,null的话就不需要关闭(out为null也去关闭,就会出现异常)
if (out != null) {
//close也有异常需要处理,这里我们选择抛出,因为系统怕该资源已经被关闭过了
out.close();
}
}
}
4.3.1 try-catch-resource
finally虽然可以用于释放资源,但是代码过于繁琐,所以JDK7和JDK9进行了改进方案
4.3.1.1 JDK7方案(常用)
JDK7的精简格式,在try()中去定义资源,当try代码块执行完毕后,会自动去关闭所定义的资源**(try()中只能使用资源,不然会报错)**
//格式如下
try(
//代码块执行完毕后会自动关闭这些定义的资源
InputStream in = new FileInputStream("a.txt");
OutputStream out = new FileOutputStream("a.txt");
) {
in.read();
out.write('a');
System.out.println("代码块执行完毕后会自动关闭资源");
} catch (IOException e) {
e.printStackTrace();
}
4.3.1.2 JDK9方案(不常用)
4.4 练习:拷贝文件夹
需求:将磁盘的文件夹拷贝到另一个文件夹下,包括文件夹中的全部信息
思路:
- 根据数据源创建字节输入和字节输出流对象
- 读写数据,复制视频
- 释放资源
- 我们需要遍历文件夹,如果是文件则拷贝过去,如果是文件夹就要进行1-1创建,继续复制内容
public static void main(String[] args) {
//创建源地址对象和目的地址对象
File file = new File("D:\\练习目录\\原地址");
File newFile = new File("D:\\练习目录\\目的地址");
//调用方法
getFile(file,newFile);
}
/**
*
* @param file 源地址对象
* @param newFile 目的地址对象
*/
public static void getFile(File file,File newFile) {
//当原路径为null 或者 原路径的文件对象不存在
if ( file == null || !file.exists()) {
// System.out.println("目录不存在!!!");
return;
}
//将原地址的当前文件名赋值给目的地址,让目的地址中有同名的文件名
String newPath = newFile.getPath() + File.separator + file.getName();
//当原路径对象为文件时(第一次进入该方法时源地址为文件时执行)
if (file.isFile()) {
//在目的地址创建该文件,并退出方法
inAndOut(file,new File(newPath));
return;
}
//只有为文件夹才会执行后面,先遍历目录得到所有文件对象
//创建同名文件夹
createDir(new File(newPath));
//将文件夹中的所有文件对象装到文件对象数组中
File[] files = file.listFiles();
//当文件夹没有权限时
if (files == null){
// System.out.println("文件夹权限不足");
return;
}
//遍历文件夹且判断是否为空的文件夹
if (files.length == 0){
// System.out.println("目录为空目录");
return;
}
//遍历文件对象数组
for (File file1 : files) {
//判断数组中的文件对象是否为文件还是文件夹
if (file1.isFile()){
//如果是文件就将文件名追加到目的地址后面
String newFilePath = newPath + File.separator + file1.getName();
//直接将源地址的文件内容写到目的地址的该文件中
inAndOut(file1,new File(newFilePath));
}else{
//如果是文件夹就继续调用getFile方法去获取文件
File newDirFile = new File(newPath);
getFile(file1,newDirFile);
}
}
}
//创建目录
public static void createDir(File newDirFile){
// System.out.println("创建了一个目录:" + newDirFile.getName());
newDirFile.mkdir();
}
//源地址的文件内容写到目的地址的同名文件中
public static void inAndOut(File oldFile,File newFile){
try (
InputStream in = new FileInputStream(oldFile);
OutputStream out = new FileOutputStream(newFile);
){
// System.out.println("创建了一个文件:" + newFile.getName());
out.write(in.readAllBytes());
}catch (Exception e){
e.printStackTrace();
}
}
五、FileFilter(文件过滤器)
FileFilter是一个接口
public interface FileFilter
//重写方法
boolean accept(File pathname)
- 当调用File类中的listFiles()方法时,支持传入FileFilter接口的实现类,对获取文件进行过滤,只有满足条件的文件才可以出现在listFiles()的返回值中。
File[] files = file.listFiles(new FilenameFilter() {
//该过滤器用来过滤文件夹中后缀.png的文件不出现在File[]中
//如果是复制文件夹,那么文件夹中.png文件不复制,其他的文件都复制
@Override
public boolean accept(File dir, String name) {
if (name.endsWith(".png")){
return false
}
return true;
}
});
File[] files = file.listFiles(new FileFilter() {
//该过滤器会过滤掉文件大小大于1M的文件(文件夹大小为0)
@Override
public boolean accept(File f) {
if (f.length() > 1024 * 1024) {
return false;
}
return true;
}
});