1 File类
1.1 概述
java.io.File
类是⽂件和⽬录路径名的抽象表示,主要⽤于⽂件和⽬录的创建、查找和删除等操作。
1.2 构造⽅法
public File(String pathname)
:通过将给定的路径名字符串转换为抽象路径名来创建新的 File 实例。public File(String parent, String child)
:从⽗路径名字符串和⼦路径名字符串创建新的 File 实例。public File(File parent, String child)
:从⽗抽象路径名和⼦路径名字符串创建新的File 实例。- 构造举例,代码如下:
// ⽂件路径名
String pathname = "D:\\aaa.txt";
File file1 = new File(pathname);
// ⽂件路径名
String pathname2 = "D:\\aaa\\bbb.txt";
File file2 = new File(pathname2);
// 通过⽗路径和⼦路径字符串
String parent = "d:\\aaa";
String child = "bbb.txt";
File file3 = new File(parent, child);
// 通过⽗级File对象和⼦路径字符串
File parentDir = new File("d:\\aaa");
String child = "bbb.txt";
File file4 = new File(parentDir, child);
⼩贴⼠:
- ⼀个File对象代表硬盘中实际存在的⼀个⽂件或者⽬录。
- ⽆论该路径下是否存在⽂件或者⽬录,都不影响 File 对象的创建。
1.3 常⽤⽅法
获取功能的⽅法
public String getAbsolutePath()
:返回此File的绝对路径名字符串。public String getPath()
:将此File转换为路径名字符串。public String getName()
:返回由此File表示的⽂件或⽬录的名称。public long length()
:返回由此File表示的⽂件的⻓度。
⽅法演示,代码如下:
public class FileGet {
public static void main(String[] args) {
File f = new File("d:/aaa/bbb.java");
System.out.println("⽂件绝对路径:" + f.getAbsolutePath());
System.out.println("⽂件构造路径:" + f.getPath());
System.out.println("⽂件名称:" + f.getName());
System.out.println("⽂件⻓度:" + f.length() + "字节");
File f2 = new File("d:/aaa");
System.out.println("⽬录绝对路径:" + f2.getAbsolutePath());
System.out.println("⽬录构造路径:" + f2.getPath());
System.out.println("⽬录名称:" + f2.getName());
System.out.println("⽬录⻓度:" + f2.length());
}
}
/*
输出结果:
⽂件绝对路径:d:\aaa\bbb.java
⽂件构造路径:d:\aaa\bbb.java
⽂件名称:bbb.java
⽂件⻓度:636字节
⽬录绝对路径:d:\aaa
⽬录构造路径:d:\aaa
⽬录名称:aaa
⽬录⻓度:4096
*/
API中说明:length(),表示⽂件的⻓度。但是File对象表示⽬录,则返回值未指定。
绝对路径和相对路径
- 绝对路径:从盘符开始的路径,这是⼀个完整的路径。
- 相对路径:相对于项⽬⽬录的路径,这是⼀个便捷的路径,开发中经常使⽤。
public class FilePath {
public static void main(String[] args) {
// D盘下的bbb.java⽂件
File f = new File("D:\\bbb.java");
System.out.println(f.getAbsolutePath());
// 项⽬下的bbb.java⽂件
File f2 = new File("bbb.java");
System.out.println(f2.getAbsolutePath());
}
}
/*
输出结果:
D:\bbb.java
D:\idea_project_test4\bbb.java
*/
判断功能的⽅法
public boolean exists()
:此File表示的⽂件或⽬录是否实际存在。public boolean isDirectory()
:此File表示的是否为⽬录。public boolean isFile()
:此File表示的是否为⽂件。
⽅法演示,代码如下:
public class FileIs {
public static void main(String[] args) {
File f = new File("d:\\aaa\\bbb.java");
File f2 = new File("d:\\aaa");
// 判断是否存在
System.out.println("d:\\aaa\\bbb.java 是否存在:" + f.exists());
System.out.println("d:\\aaa 是否存在:" + f2.exists());
// 判断是⽂件还是⽬录
System.out.println("d:\\aaa ⽂件?:" + f2.isFile());
System.out.println("d:\\aaa ⽬录?:" + f2.isDirectory());
}
}
/*
输出结果:
d:\aaa\bbb.java 是否存在:true
d:\aaa 是否存在:true
d:\aaa ⽂件?:false
d:\aaa ⽬录?:true
*/
创建删除功能的⽅法
public boolean createNewFile()
:当且仅当具有该名称的⽂件尚不存在时,创建⼀个新的空⽂件。public boolean delete()
:删除由此File表示的⽂件或⽬录。public boolean mkdir()
:创建由此File表示的⽬录。public boolean mkdirs()
:创建由此File表示的⽬录,包括任何必需但不存在的⽗⽬录。
⽅法演示,代码如下:
public class FileCreateDelete {
public static void main(String[] args) throws IOException {
// ⽂件的创建
File f = new File("aaa.txt");
System.out.println("是否存在:" + f.exists()); // false
System.out.println("是否创建:" + f.createNewFile()); // true
System.out.println("是否存在:" + f.exists()); // true
// ⽬录的创建
File f2= new File("newDir");
System.out.println("是否存在:" + f2.exists()); // false
System.out.println("是否创建:" + f2.mkdir()); // true
System.out.println("是否存在:" + f2.exists()); // true
// 创建多级⽬录
File f3= new File("newDira\\newDirb");
System.out.println(f3.mkdir()); // false
File f4= new File("newDira\\newDirb");
System.out.println(f4.mkdirs()); // true
// ⽂件的删除
System.out.println(f.delete()); // true
// ⽬录的删除
System.out.println(f2.delete()); // true
System.out.println(f4.delete()); // false
}
}
API中说明:delete⽅法,如果此File表示⽬录,则⽬录必须为空才能删除。
1.4 ⽬录的遍历
public String[] list()
:返回⼀个String数组,表示该File⽬录中的所有⼦⽂件或⽬录。public File[] listFiles()
:返回⼀个File数组,表示该File⽬录中的所有的⼦⽂件或⽬录。
public class FileFor {
public static void main(String[] args) {
File dir = new File("d:\\java_code");
// 获取当前⽬录下的⽂件以及⽂件夹的名称。
String[] names = dir.list();
for (String name : names) {
System.out.println(name);
}
// 获取当前⽬录下的⽂件以及⽂件夹对象,只要拿到了⽂件对象,那么就可以获取更多信息
File[] files = dir.listFiles();
for (File file : files) {
System.out.println(file);
}
}
}
⼩贴⼠:
调⽤listFiles⽅法的File对象,表示的必须是实际存在的⽬录,否则返回null,⽆法进⾏遍历。
2 递归
2.1 概述
- 递归:指在当前⽅法内调⽤⾃⼰的这种现象。
- 递归的分类:
- 递归分为两种,直接递归和间接递归。
- 直接递归称为⽅法⾃身调⽤⾃⼰。
- 间接递归可以A⽅法调⽤B⽅法,B⽅法调⽤C⽅法,C⽅法调⽤A⽅法。
- 注意事项:
- 递归⼀定要有条件限定,保证递归能够停⽌下来,否则会发⽣栈内存溢出。
- 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发⽣栈内存溢出。
- 构造⽅法,禁⽌递归
public class Demo01DiGui {
public static void main(String[] args) {
// a();
b(1);
}
/*
* 3.构造⽅法,禁⽌递归
* 编译报错:构造⽅法是创建对象使⽤的,不能让对象⼀直创建下去
*/
public Demo01DiGui() {
// Demo01DiGui();
}
/*
* 2.在递归中虽然有限定条件,但是递归次数不能太多。否则也会发⽣栈内存溢出。
* 4993
* Exception in thread "main" java.lang.StackOverflowError
*/
private static void b(int i) {
System.out.println(i);
//添加⼀个递归结束的条件,i==5000的时候结束
if (i == 5000) {
return; // 结束⽅法
}
b(++i);
}
/*
* 1.递归⼀定要有条件限定,保证递归能够停⽌下来,否则会发⽣栈内存溢出。
Exception in thread "main"
* java.lang.StackOverflowError
*/
private static void a() {
System.out.println("a⽅法");
a();
}
}
2.2 递归累加求和
计算1 ~ n的和
分析:num的累和 = num + (num-1)的累和,所以可以把累和的操作定义成⼀个⽅法,递归调⽤。
实现代码:
public class DiGui {
public static void main(String[] args) {
//计算1~num的和,使⽤递归完成
int a = 10;
// 调⽤求和的⽅法
int sum = getSum(a);
// 输出结果
System.out.println(sum);
}
/*
* 通过递归算法实现.
* 参数列表:int
* 返回值类型:int
*/
private static int getSum(int a) {
/*
* num为1时,⽅法返回1,
* 相当于是⽅法的出⼝,num总有是1的情况
*/
if (a == 1) {
return 1;
}
/*
* a不为1时,⽅法返回 a +(--a)的累和
* 递归调⽤getSum⽅法
*/
return a + getSum(--a);
}
}
代码执⾏图解
⼩贴⼠:递归⼀定要有条件限定,保证递归能够停⽌下来,次数不要太多,否则会发⽣栈内存溢出。
2.3 递归求阶乘
-
阶乘:所有⼩于及等于该数的正整数的积。
n的阶乘:n! = n * (n-1) *...* 3 * 2 * 1
分析:这与累和类似,只不过换成了乘法运算,学员可以⾃⼰练习,需要注意阶乘值符合int类型的范围。
推理得出:n! = n * (n-1)!
代码实现:
public class DiGui {
//计算n的阶乘,使⽤递归完成
public static void main(String[] args) {
int a = 5;
// 调⽤求阶乘的⽅法
int sum = getValue(a);
// 输出结果
System.out.println(sum);
}
/*
* 通过递归算法实现.
* 参数列表:int
* 返回值类型:int
*/
private static int getValue(int a) {
// 1的阶乘为1
if (a == 1)
return a;
/*
* n不为1时,⽅法返回 n! = n*(n-1)!
* 递归调⽤getValue⽅法
*/
return a * getValue(--a);
}
}
2.4 递归打印多级⽬录
分析:多级⽬录的打印,就是当⽬录的嵌套。遍历之前,⽆从知道到底有多少级⽬录,所以我们还是要使⽤递归实现。
代码实现:
public class DiGuiDemo2 {
public static void main(String[] args) {
// 创建File对象
File dir = new File("D:\\aaa");
// 调⽤打印⽬录⽅法
printDir(dir);
}
public static void printDir(File dir) {
// 获取⼦⽂件和⽬录
File[] files = dir.listFiles();
// 循环打印
/*
* 判断:
* 当是⽂件时,打印绝对路径.
* 当是⽬录时,继续调⽤打印⽬录的⽅法,形成递归调⽤.
*/
for (File file : files) {
// 判断
if (file.isFile()) {
// 是⽂件,输出⽂件绝对路径
System.out.println("⽂件名:" + file.getAbsolutePath());
} else {
// 是⽬录,输出⽬录绝对路径
System.out.println("⽬录:" + file.getAbsolutePath());
// 继续遍历,调⽤printDir,形成递归
printDir(file);
}
}
}
}
3 综合案例
3.1 ⽂件搜索
搜索 D:\aaa ⽬录中的 .java ⽂件。
分析:
- ⽬录搜索,⽆法判断多少级⽬录,所以使⽤递归,遍历所有⽬录。
- 遍历⽬录时,获取的⼦⽂件,通过⽂件名称,判断是否符合条件。
代码实现:
public class DiGuiDemo3 {
public static void main(String[] args) {
// 创建File对象
File dir = new File("D:\\aaa");
// 调⽤打印⽬录⽅法
printDir(dir);
}
public static void printDir(File dir) {
// 获取⼦⽂件和⽬录
File[] files = dir.listFiles();
// 循环打印
for (File file : files) {
if (file.isFile()) {
// 是⽂件,判断⽂件名并输出⽂件绝对路径
if (file.getName().endsWith(".java")) {
System.out.println("⽂件名:" + file.getAbsolutePath());
}
} else {
// 是⽬录,继续遍历,形成递归
printDir(file);
}
}
}
}
3.2 ⽂件过滤器优化
java.io.FileFilter
是⼀个接⼝,是File的过滤器。 该接⼝的对象可以传递给File类的listFiles(FileFilter)
作为参数, 接⼝中只有⼀个⽅法。
boolean accept(File pathname)
:测试pathname是否应该包含在当前File⽬录中,符合则返回true。
分析:
- 接⼝作为参数,需要传递⼦类对象,重写其中⽅法。我们选择匿名内部类⽅式,⽐较简单。
- accept ⽅法,参数为File,表示当前File下所有的⼦⽂件和⼦⽬录。保留住则返回true,过滤掉则返回false。保留规则:
- 要么是.java⽂件。
- 要么是⽬录,⽤于继续遍历。
- 通过过滤器的作⽤, listFiles(FileFilter) 返回的数组元素中,⼦⽂件对象都是符合条件的,可以直接打印。
代码实现
public class DiGuiDemo4 {
public static void main(String[] args) {
File dir = new File("D:\\aaa");
printDir2(dir);
}
public static void printDir2(File dir) {
// 匿名内部类⽅式,创建过滤器⼦类对象
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith(".java") ||
pathname.isDirectory();
}
});
// 循环打印
for (File file : files) {
if (file.isFile()) {
System.out.println("⽂件名:" + file.getAbsolutePath());
} else {
printDir2(file);
}
}
}
}
### 3.3 Lambda优化
分析: FileFilter 是只有⼀个⽅法的接⼝,因此可以⽤lambda表达式简写。
lambda格式:
() -> { }
代码实现:
public static void printDir3(File dir) {
// lambda的改写
File[] files = dir.listFiles(f ->{
return f.getName().endsWith(".java") || f.isDirectory();
});
// 循环打印
for (File file : files) {
if (file.isFile()) {
System.out.println("⽂件名:" + file.getAbsolutePath());
} else {
printDir3(file);
}
}
}
4 IO概述
4.1 什么是IO
⽣活中,你肯定经历过这样的场景。当你编辑⼀个⽂本⽂件,忘记了 ctrl+s ,可能⽂件就⽩⽩编辑了。当你电脑上插⼊⼀个U盘,可以把⼀个视频,拷⻉到你的电脑硬盘⾥。那么数据都是在哪些设备上的呢?比如键盘、内存、硬盘、外接设备等等。
我们把这种数据的传输,可以看做是⼀种数据的流动,按照流动的⽅向,以内存为基准,分为输⼊input
和输出output
,即流向内存是输⼊流,流出内存的输出流。
Java中I/O操作主要是指使⽤ java.io
包下的内容,进⾏输⼊、输出操作。输⼊也叫做读取数据,输出也叫做作写出数据。
4.2 IO的分类
根据数据的流向分为:输⼊流和输出流。
- 输⼊流 :把数据从
其他设备
上读取到内存
中的流。 - 输出流 :把数据从
内存
中写出到其他设备
上的流。
格局数据的类型分为:字节流和字符流。
- 字节流 :以字节为单位,读写数据的流。
- 字符流 :以字符为单位,读写数据的流。
4.3 IO的流向说明图解
4.4 顶级⽗类们
5 字节流
5.1 ⼀切皆为字节
⼀切⽂件数据(⽂本、图⽚、视频等)在存储时,都是以⼆进制数字的形式保存,都⼀个⼀个的字节,那么传输时⼀样如此。所以,字节流可以传输任意⽂件数据。在操作流的时候,我们要时刻明确,⽆论使⽤什么样的流对象,底层传输的始终为⼆进制数据。
5.2 字节输出流【OutputStream】
java.io.OutputStream
抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到⽬的地。它定义了字节输出流的基本共性功能⽅法。
public void close()
:关闭此输出流并释放与此流相关联的任何系统资源。public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出。public void write(byte[] b)
:将 b.length字节从指定的字节数组写⼊此输出流。public void write(byte[] b, int off, int len)
:从指定的字节数组写⼊ len 字节,从偏移量 off 开始输出到此输出流。public abstract void write(int b)
:将指定的字节输出流。
⼩贴⼠:
close⽅法,当完成流的操作时,必须调⽤此⽅法,释放系统资源。
5.3 FileOutputStream类
OutputStream
有很多⼦类,我们从最简单的⼀个⼦类开始。
java.io.FileOutputStream
类是⽂件输出流,⽤于将数据写出到⽂件。
构造⽅法
public FileOutputStream(File file)
:创建⽂件输出流以写⼊由指定的 File对象表示的⽂件。public FileOutputStream(String name)
:创建⽂件输出流以指定的名称写⼊⽂件。
当你创建⼀个流对象时,必须传⼊⼀个⽂件路径。该路径下,如果没有这个⽂件,会创建该⽂件。如果有这个⽂件,会清空这个⽂件的数据。
- 构造举例,代码如下:
public class FileOutputStreamConstructor {
public static void main(String[] args) throws FileNotFoundException {
// 使⽤File对象创建流对象
File file = new File("a.txt");
FileOutputStream fos1 = new FileOutputStream(file);
// 使⽤⽂件名称创建流对象
FileOutputStream fos2 = new FileOutputStream("b.txt");
}
}
写出字节数据
- 写出字节: write(int b) ⽅法,每次可以写出⼀个字节数据,代码使⽤演示:
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使⽤⽂件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 写出数据
fos.write(97); // 写出第1个字节
fos.write(98); // 写出第2个字节
fos.write(99); // 写出第3个字节
// 关闭资源
fos.close();
}
}
//输出结果:
//abc
⼩贴⼠:
- 虽然参数为int类型四个字节,但是只会保留⼀个字节的信息写出。
- 流操作完毕后,必须释放系统资源,调⽤close⽅法,千万记得。
- 写出字节数组: write(byte[] b) ,每次可以写出数组中的数据,代码使⽤演示:
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使⽤⽂件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 字符串转换为字节数组
byte[] b = "Java程序员".getBytes();
// 写出字节数组数据
fos.write(b);
// 关闭资源
fos.close();
}
}
//输出结果:
//Java程序员
- 写出指定⻓度字节数组: write(byte[] b, int off, int len) ,每次写出从off索引开始,len个字节,代码使⽤演示:
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使⽤⽂件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 字符串转换为字节数组
byte[] b = "abcde".getBytes();
// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
fos.write(b, 2, 2);
// 关闭资源
fos.close();
}
}
//输出结果:
//cd
数据追加续写
经过以上的演示,每次程序运⾏,创建输出流对象,都会清空⽬标⽂件中的数据。如何保留⽬标⽂件中数据,还能继续添加新数据呢?
public FileOutputStream(File file, boolean append)
: 创建⽂件输出流以写⼊由指定的 File对象表示的⽂件。public FileOutputStream(String name, boolean append)
:创建⽂件输出流以指定的名称写⼊⽂件。
这两个构造⽅法,参数中都需要传⼊⼀个boolean类型的值, true 表示追加数据, false 表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了,代码使⽤演示:
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使⽤⽂件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt",true);
// 字符串转换为字节数组
byte[] b = "abcde".getBytes();
// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
fos.write(b);
// 关闭资源
fos.close();
}
}
//⽂件操作前:cd
//⽂件操作后:cdabcde
写出换⾏
Windows系统⾥,换⾏符号是 \r\n 。
代码使⽤演示:
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使⽤⽂件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 定义字节数组
byte[] words = { 97, 98, 99, 100, 101 };
// 遍历数组
for (int i = 0; i < words.length; i++) {
// 写出⼀个字节
fos.write(words[i]);
// 写出⼀个换⾏, 换⾏符号转成数组写出
fos.write("\r\n".getBytes());
}
// 关闭资源
fos.close();
}
}
//输出结果:
//a
//b
//c
//d
//e
- 回⻋符 \r 和换⾏符 \n :
回⻋符:回到⼀⾏的开头(return)。
换⾏符:下⼀⾏(newline)。- 系统中的换⾏:
Windows系统⾥,每⾏结尾是 回⻋+换⾏ ,即 \r\n ;
Unix系统⾥,每⾏结尾只有 换⾏ ,即 \n ;
Mac系统⾥,每⾏结尾是 回⻋ ,即 \r 。从 Mac OS X开始与Linux统⼀。
5.4 字节输⼊流【InputStream】
java.io.InputStream
抽象类是表示字节输⼊流的所有类的超类,可以读取字节信息到内存中。它定义了字节输⼊流的基本共性功能⽅法。
public void close()
:关闭此输⼊流并释放与此流相关联的任何系统资源。public abstract int read()
:从输⼊流读取数据的下⼀个字节。public int read(byte[] b)
:从输⼊流中读取⼀些字节数,并将它们存储到字节数组 b中 。
⼩贴⼠:
close⽅法,当完成流的操作时,必须调⽤此⽅法,释放系统资源。
5.5 FileInputStream类
java.io.FileInputStream
类是⽂件输⼊流,从⽂件中读取字节。
构造⽅法
FileInputStream(File file)
:通过打开与实际⽂件的连接来创建⼀个 FileInputStream,该⽂件由⽂件系统中的 File对象 file命名。FileInputStream(String name)
:通过打开与实际⽂件的连接来创建⼀个FileInputStream ,该⽂件由⽂件系统中的路径名 name命名。
当你创建⼀个流对象时,必须传⼊⼀个⽂件路径。该路径下,如果没有该⽂件,会抛出FileNotFoundException 。
- 构造举例,代码如下:
public class FileInputStreamConstructor {
public static void main(String[] args) throws FileNotFoundException {
// 使⽤File对象创建流对象
File file = new File("a.txt");
FileInputStream fos1 = new FileInputStream(file);
// 使⽤⽂件名称创建流对象
FileInputStream fos2 = new FileInputStream("b.txt");
}
}
读取字节数据
- 读取字节:
read
⽅法,每次可以读取⼀个字节的数据,提升为int类型,读取到⽂件末尾,返回-1
,代码使⽤演示:
public class FISRead {
public static void main(String[] args) throws IOException{
// 使⽤⽂件名称创建流对象
FileInputStream fis = new FileInputStream("read.txt");
// 读取数据,返回⼀个字节
int read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
// 读取到末尾,返回-1
read = fis.read();
System.out.println(read);
// 关闭资源
fis.close();
}
}
//输出结果:
//a
//b
//c
//d
//e
//-1
循环改进读取⽅式,代码使⽤演示:
public class FISRead {
public static void main(String[] args) throws IOException{
// 使⽤⽂件名称创建流对象
FileInputStream fis = new FileInputStream("read.txt");
// 定义变量,保存数据
int b;
// 循环读取
while ((b = fis.read()) != -1) {
System.out.println((char)b);
}
// 关闭资源
fis.close();
}
}
//输出结果:
//a
//b
//c
//d
//e
⼩贴⼠:
- 虽然读取了⼀个字节,但是会⾃动提升为int类型。
- 流操作完毕后,必须释放系统资源,调⽤close⽅法,千万记得。
- 使⽤字节数组读取: read(byte[] b) ,每次读取b的⻓度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回 -1 ,代码使⽤演示:
public class FISRead {
public static void main(String[] args) throws IOException {
// 使⽤⽂件名称创建流对象.
FileInputStream fis = new FileInputStream("read.txt"); // ⽂件中为abcde
// 定义变量,作为有效个数
int len;
// 定义字节数组,作为装字节数据的容器
byte[] b = new byte[2];
// 循环读取
while (( len= fis.read(b)) != -1) {
// 每次读取后,把数组变成字符串打印
System.out.println(new String(b));
}
// 关闭资源
fis.close();
}
}
//输出结果:
//ab
//cd
//ed
错误数据 d ,是由于最后⼀次读取时,只读取⼀个字节 e ,数组中,上次读取的数据没有被完全替换,所以要通过 len ,获取有效的字节,代码使⽤演示:
public class FISRead {
public static void main(String[] args) throws IOException{
// 使⽤⽂件名称创建流对象.
FileInputStream fis = new FileInputStream("read.txt"); // ⽂件中为abcde
// 定义变量,作为有效个数
int len;
// 定义字节数组,作为装字节数据的容器
byte[] b = new byte[2];
// 循环读取
while (( len= fis.read(b)) != -1) {
// 每次读取后,把数组的有效字节部分,变成字符串打印
System.out.println(new String(b, 0, len));// len 每次读取的有效字节个数
}
// 关闭资源
fis.close();
}
}
//输出结果:
//ab
//cd
//e
⼩贴⼠:
使⽤数组读取,每次读取多个字节,减少了系统间的IO操作次数,从⽽提⾼了读写的效率,建议开发中使⽤。
5.6 字节流练习:图⽚复制
复制原理图解
案例实现
复制图⽚⽂件,代码使⽤演示:
public class Copy {
public static void main(String[] args) throws IOException {
// 1.创建流对象
// 1.1 指定数据源
FileInputStream fis = new FileInputStream("D:\\test.jpg");
// 1.2 指定⽬的地
FileOutputStream fos = new FileOutputStream("test_copy.jpg");
// 2.读写数据
// 2.1 定义数组
byte[] b = new byte[1024];
// 2.2 定义⻓度
int len;
// 2.3 循环读取
while ((len = fis.read(b)) != -1) {
// 2.4 写出数据
fos.write(b, 0, len);
}
// 3.关闭资源
fos.close();
fis.close();
}
}
⼩贴⼠:
流的关闭原则:先开后关,后开先关。
6 字符流
当使⽤字节流读取⽂本⽂件时,可能会有⼀个⼩问题。就是遇到中⽂字符时,可能不会显示完整的字符,那是因为⼀个中⽂字符可能占⽤多个字节存储。所以 Java 提供⼀些字符流类,以字符为单位读写数据,专⻔⽤于处理⽂本⽂件。
6.1 字符输⼊流【Reader】
java.io.Reader
抽象类是表示⽤于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输⼊流的基本共性功能⽅法。
public void close()
:关闭此流并释放与此流相关联的任何系统资源。public int read()
:从输⼊流读取⼀个字符。public int read(char[] cbuf)
:从输⼊流中读取⼀些字符,并将它们存储到字符数组cbuf 中 。
6.2 FileReader类
java.io.FileReader
类是读取字符⽂件的便利类。构造时使⽤系统默认的字符编码和默认字节缓冲区。
⼩贴⼠:
- 字符编码:字节与字符的对应规则。Windows系统的中⽂编码默认是GBK编码表。idea中UTF-8
- 字节缓冲区:⼀个字节数组,⽤来临时存储字节数据。
构造⽅法
- FileReader(File file) :创建⼀个新的 FileReader ,给定要读取的File对象。
- FileReader(String fileName) :创建⼀个新的 FileReader ,给定要读取的⽂件的名称。
当你创建⼀个流对象时,必须传⼊⼀个⽂件路径。类似于FileInputStream。
- 构造举例,代码如下:
public class FileReaderConstructor throws IOException{
public static void main(String[] args) {
// 使⽤File对象创建流对象
File file = new File("a.txt");
FileReader fr = new FileReader(file);
// 使⽤⽂件名称创建流对象
FileReader fr = new FileReader("b.txt");
}
}
读取字符数据
- 读取字符:
read
⽅法,每次可以读取⼀个字符的数据,提升为int类型,读取到⽂件末尾,返回-1
,循环读取,代码使⽤演示:
public class FRRead {
public static void main(String[] args) throws IOException {
// 使⽤⽂件名称创建流对象
FileReader fr = new FileReader("read.txt");
// 定义变量,保存数据
int b;
// 循环读取
while ((b = fr.read()) != -1) {
System.out.println((char)b);
}
// 关闭资源
fr.close();
}
}
//输出结果:
//程
//序
//员
⼩贴⼠:虽然读取了⼀个字符,但是会⾃动提升为int类型。
- 使⽤字符数组读取: read(char[] cbuf) ,每次读取b的⻓度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回 -1 ,代码使⽤演示:
public class FRRead {
public static void main(String[] args) throws IOException {
// 使⽤⽂件名称创建流对象
FileReader fr = new FileReader("read.txt");
// 定义变量,保存有效字符个数
int len;
// 定义字符数组,作为装字符数据的容器
char[] cbuf = new char[2];
// 循环读取
while ((len = fr.read(cbuf)) != -1) {
System.out.println(new String(cbuf));
}
// 关闭资源
fr.close();
}
}
//输出结果:
//程序
//员序
获取有效的字符改进,代码使⽤演示:
public class FISRead {
public static void main(String[] args) throws IOException {
// 使⽤⽂件名称创建流对象
FileReader fr = new FileReader("read.txt");
// 定义变量,保存有效字符个数
int len;
// 定义字符数组,作为装字符数据的容器
char[] cbuf = new char[2];
// 循环读取
while ((len = fr.read(cbuf)) != -1) {
System.out.println(new String(cbuf,0,len));
}
// 关闭资源
fr.close();
}
}
//输出结果:
//程序
//员
6.3 字符输出流【Writer】
java.io.Writer
抽象类是表示⽤于写出字符流的所有类的超类,将指定的字符信息写出到⽬的地。它定义了字节输出流的基本共性功能⽅法。
void write(int c)
:写⼊单个字符。void write(char[] cbuf)
:写⼊字符数组。abstract void write(char[] cbuf, int off, int len)
:写⼊字符数组的某⼀部分,off数组的开始索引,len写的字符个数。void write(String str)
:写⼊字符串。void write(String str, int off, int len)
:写⼊字符串的某⼀部分,off字符串的开始索引,len写的字符个数。void flush()
:刷新该流的缓冲。void close()
:关闭此流,但要先刷新它。
6.4 FileWriter类
java.io.FileWriter
类是写出字符到⽂件的便利类。构造时使⽤系统默认的字符编码和默认字节缓冲区。
构造⽅法
FileWriter(File file)
:创建⼀个新的 FileWriter,给定要读取的File对象。FileWriter(String fileName)
:创建⼀个新的 FileWriter,给定要读取的⽂件的名称。
当你创建⼀个流对象时,必须传⼊⼀个⽂件路径,类似于FileOutputStream。- 构造举例,代码如下:
public class FileWriterConstructor {
public static void main(String[] args) throws IOException {
// 使⽤File对象创建流对象
File file = new File("a.txt");
FileWriter fw = new FileWriter(file);
// 使⽤⽂件名称创建流对象
FileWriter fw = new FileWriter("b.txt");
}
}
基本写出数据
写出字符: write(int b)
⽅法,每次可以写出⼀个字符数据,代码使⽤演示:
public class FWWrite {
public static void main(String[] args) throws IOException {
// 使⽤⽂件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 写出数据
fw.write(97); // 写出第1个字符
fw.write('b'); // 写出第2个字符
fw.write('C'); // 写出第3个字符
fw.write(30000); // 写出第4个字符,中⽂编码表中30000对应⼀个汉字。
/*
*【注意】关闭资源时,与FileOutputStream不同。
* 如果不关闭,数据只是保存到缓冲区,并未保存到⽂件。
*/
// fw.close();
}
}
//输出结果:
//abC⽥
⼩贴⼠:
- 虽然参数为int类型四个字节,但是只会保留⼀个字符的信息写出。
- 未调⽤close⽅法,数据只是保存到了缓冲区,并未写出到⽂件中。
关闭和刷新
因为内置缓冲区的原因,如果不关闭输出流,⽆法写出字符到⽂件中。但是关闭的流对象,是⽆法继续写出数据的。如果我们既想写出数据,⼜想继续使⽤流,就需要 flush ⽅法了。
flush
:刷新缓冲区,流对象可以继续使⽤。close
:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使⽤了。
代码使⽤演示:
public class FWWrite {
public static void main(String[] args) throws IOException {
// 使⽤⽂件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 写出数据,通过flush
fw.write('刷'); // 写出第1个字符
fw.flush();
fw.write('新'); // 继续写出第2个字符,写出成功
fw.flush();
// 写出数据,通过close
fw.write('关'); // 写出第1个字符
fw.close();
fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream
closed
fw.close();
}
}
⼩贴⼠:即便是flush⽅法写出了数据,操作的最后还是要调⽤close⽅法,释放系统资源。
写出其他数据
- 写出字符数组:
write(char[] cbuf)
和write(char[] cbuf, int off, int len)
,每次可以写出字符数组中的数据,⽤法类似FileOutputStream,代码使⽤演示:
public class FWWrite {
public static void main(String[] args) throws IOException {
// 使⽤⽂件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 字符串转换为字节数组
char[] chars = "程序员".toCharArray();
// 写出字符数组
fw.write(chars); // 程序员
// 写出从索引1开始,2个字符。索引1是'序',两个字符,也就是'序员'。
fw.write(b, 1, 2); // 序员
// 关闭资源
fos.close();
}
}
- 写出字符串:
write(String str)
和write(String str, int off, int len)
,每次可以写出字符串中的数据,更为⽅便,代码使⽤演示:
public class FWWrite {
public static void main(String[] args) throws IOException {
// 使⽤⽂件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 字符串
String msg = "程序员";
// 写出字符数组
fw.write(msg); //程序员
// 写出从索引1开始,2个字符。索引1是'序',两个字符,也就是'序员'。
fw.write(msg, 1, 2); // 序员
// 关闭资源
fos.close();
}
}
- 续写和换⾏:操作类似于FileOutputStream。
public class FWWrite {
public static void main(String[] args) throws IOException {
// 使⽤⽂件名称创建流对象,可以续写数据
FileWriter fw = new FileWriter("fw.txt",true);
// 写出字符串
fw.write("Java");
// 写出换⾏
fw.write("\r\n");
// 写出字符串
fw.write("程序员");
// 关闭资源
fw.close();
}
}
//输出结果:
//Java
///程序员
⼩贴⼠:字符流,只能操作⽂本⽂件,不能操作图⽚,视频等⾮⽂本⽂件。
当我们单纯读或者写⽂本⽂件时 使⽤字符流 其他情况使⽤字节流
7 IO异常的处理
7.1 JDK7 前处理
之前的⼊⻔练习,我们⼀直把异常抛出,⽽实际开发中并不能这样处理,建议使⽤ try…catch…finally 代码块,处理异常部分,代码使⽤演示:
public class HandleException1 {
public static void main(String[] args) {
// 声明变量
FileWriter fw = null;
try {
// 创建流对象
fw = new FileWriter("fw.txt");
// 写出数据
fw.write("Java程序员"); // Java程序员
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
7.2 JDK7的处理(扩展知识点了解内容)
还可以使⽤JDK7优化后的 try-with-resource
语句,该语句确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。
格式:
try (创建流对象语句,如果多个,使⽤';'隔开) {
// 读写数据
} catch (IOException e) {
e.printStackTrace();
}
代码使⽤演示:
public class HandleException2 {
public static void main(String[] args) {
// 创建流对象
try ( FileWriter fw = new FileWriter("fw.txt"); ) {
// 写出数据
fw.write("Java程序员"); // Java程序员
} catch (IOException e) {
e.printStackTrace();
}
}
}
7.3 JDK9的改进(扩展知识点了解内容)
JDK9中 try-with-resource
的改进,对于引⼊对象的⽅式,⽀持的更加简洁。被引⼊的对象,同样可以⾃动关闭,⽆需⼿动close,我们来了解⼀下格式。
改进前格式:
// 被final修饰的对象
final Resource resource1 = new Resource("resource1");
// 普通对象
Resource resource2 = new Resource("resource2");
// 引⼊⽅式:创建新的变量保存
try (Resource r1 = resource1;
Resource r2 = resource2) {
// 使⽤对象
}
改进后格式:
// 被final修饰的对象
final Resource resource1 = new Resource("resource1");
// 普通对象
Resource resource2 = new Resource("resource2");
// 引⼊⽅式:直接引⼊
try (resource1; resource2) {
// 使⽤对象
}
改进后,代码使⽤演示:
public class TryDemo {
public static void main(String[] args) throws IOException {
// 创建流对象
final FileReader fr = new FileReader("in.txt");
FileWriter fw = new FileWriter("out.txt");
// 引⼊到try中
try (fr; fw) {
// 定义变量
int b;
// 读取数据
while ((b = fr.read()) != -1) {
// 写出数据
fw.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
8 属性集
8.1 概述
java.util.Properties
继承于 Hashtable
,来表示⼀个持久的属性集。它使⽤键值结构存储数据,每个键及其对应值都是⼀个字符串。该类也被许多Java类使⽤,⽐如获取系统属性时, System.getProperties
⽅法就是返回⼀个 Properties
对象。
8.2 Properties类
构造⽅法
public Properties()
:创建⼀个空的属性列表。
基本的存储⽅法
public Object setProperty(String key, String value)
:保存⼀对属性。public String getProperty(String key)
:使⽤此属性列表中指定的键搜索属性值。public Set<String> stringPropertyNames()
:所有键的名称的集合。
public class ProDemo {
public static void main(String[] args) throws FileNotFoundException {
// 创建属性集对象
Properties properties = new Properties();
// 添加键值对元素
properties.setProperty("filename", "a.txt");
properties.setProperty("length", "209385038");
properties.setProperty("location", "D:\\a.txt");
// 打印属性集对象
System.out.println(properties);
// 通过键,获取属性值
System.out.println(properties.getProperty("filename"));
System.out.println(properties.getProperty("length"));
System.out.println(properties.getProperty("location"));
// 遍历属性集,获取所有键的集合
Set<String> strings = properties.stringPropertyNames();
// 打印键值对
for (String key : strings ) {
System.out.println(key + " -- " + properties.getProperty(key));
}
}
}
/*
输出结果:
{filename=a.txt, length=209385038, location=D:\a.txt} a.txt
209385038
D:\a.txt
filename -- a.txt
length -- 209385038
location -- D:\a.txt
*/
与流相关的⽅法
public void load(InputStream inStream)
:从字节输⼊流中读取键值对。
参数中使⽤了字节输⼊流,通过流对象,可以关联到某⽂件上,这样就能够加载⽂本中的数据了。⽂本数据格式:
filename=a.txt
length=209385038
location=D:\a.txt
加载代码演示:
public class ProDemo2 {
public static void main(String[] args) throws FileNotFoundException {
// 创建属性集对象
Properties pro = new Properties();
// 加载⽂本中信息到属性集
pro.load(new FileInputStream("read.txt"));
// 遍历集合并打印
Set<String> strings = pro.stringPropertyNames();
for (String key : strings ) {
System.out.println(key + " -- " + pro.getProperty(key));
}
}
}
/*
输出结果:
filename -- a.txt
length -- 209385038
location -- D:\a.txt
*/
⼩贴⼠:⽂本中的数据,必须是键值对形式,可以使⽤空格、等号、冒号等符号分隔。