目录
一、认识文件和相关知识
1.认识文件
2.⽬录
3.⽂件路径(Path)
4.文本文件和二进制文件的区分
二、File类操作文件
1.构造方法
2.方法
2.1 方法表
2.2 get相关的方法和构造方法
2.2.1 “.” 和 “..”
2.3 is相关的方法
2.4 删除相关的方法
2.5 返回当前目录下的文件
2.5.1 递归实现遍历路径下所有文件
2.6 创建目录
2.7 renameTo()
三、流 (Stream)
1.初时“流”
2.输入输出的角度
3.字节流的操作
3.1 InputStream
3.1.1 FileInputStream
3.1.1.1 read()
3.1.1.2 read(byte[] b)
3.1.1.3 read(byte[] b,int off,int len)
3.1.1.4 Scanner获取文件内容
3.2 OutputStream
3.2.1 FileOutputStream
3.2.1.1 write(int b)
3.2.1.2 write(byte[] b)
3.2.1.3 write(byte[] b, int off, int len)
4.字符流的操作
4.1 Reader
4.1.1 FileReader
4.1.1.1 read()
4.1.1.2 read(char[] cbuf)
4.1.1.3 read(char[] cbuf,int off,int len)
4.2 Writer
4.2.1FileWriter
4.2.1.1 write(String str)
4.2.1.2 write(char cbuf[])
4.2.1.3 write(char cbuf[], int off,int len)
四、案例练习
1.扫描指定⽬录,并找到名称中包含指定字符的所有普通⽂件(不包含⽬录),并且后续询问⽤⼾是否要删除该⽂件
2.复制普通文件
3.扫描指定⽬录,并找到内容中包含指定字符的所有普通⽂件(不包含⽬录)
一、认识文件和相关知识
1.认识文件
我们先来认识狭义上的⽂件(file)。针对硬盘这种持久化存储的I/O设备,当我们想要进⾏数据保存时,往往不是保存成⼀个整体,⽽是独⽴成⼀个个的单位进⾏保存,这个独⽴的单位就被抽象成⽂件的概念,就类似办公桌上的⼀份份真实的⽂件⼀般。简单来说就是,文件是保存在硬盘上的,但是硬盘非常大,硬盘就用逻辑抽象成一个文件。
⽂件除了有数据内容之外,还有⼀部分信息,例如⽂件名、⽂件类型、⽂件⼤⼩等并不作为⽂件的数据⽽存在,我们把这部分信息可以视为⽂件的元信息。
2.⽬录
同时,随着⽂件越来越多,对⽂件的系统管理也被提上了⽇程 ,如何进⾏⽂件的组织呢,⼀种合乎⾃ 然的想法出现了,就是按照层级结构进⾏组织⸺也就是我们数据结构中学习过的树形结构。这样,⼀种专⻔⽤来存放管理信息的特殊⽂件诞⽣了,也就是我们平时所谓⽂件夹(folder)或者⽬录(directory)的概念。同时也可以看出,数据结构是n叉树![]()
3.⽂件路径(Path)
如何在⽂件系统中如何定位我们的⼀个唯⼀的⽂件就成为当前要解决的问题,但这难不倒计算机科学家,因为从树型结构的⻆度来看,树中的每个结点都可以被⼀条从根开始,⼀直到达的结点的路径所描述,⽽这种描述⽅式就被称为⽂件的绝对路径(absolute path)。例如我要查找这个文件:
绝对路径:C:\Program Files\Java\jdk-11除了可以从根开始进⾏路径的描述,我们可以从任意结点出发,进⾏路径的描述,⽽这种描述⽅式就被称为相对路径(relative path),相对于当前所在结点的⼀条路径。相对路径,结合之后的代码讲解
4.文本文件和二进制文件的区分
我们其实就可以通过记事本打开的方式查看。
- 如果打开是我们认识的一些东西,包括我们写的.java文件,它大概率就是文本文件。
- 如果通过记事本打开的文件是一堆乱码,也就是二进制文件。
所有的文件存储都是二进制,哪怕是文本文件,它底层存储的仍然是二进制,那为什么是我们可以看懂的文字?
因为文本文件会有一个查表的动作,这个“表”是把对应的二进制转换为字符,例如:ASII码表、UTF8、Unicode等
二、File类操作文件
Java 中通过 java.io.File 类来对⼀个⽂件(包括⽬录)进⾏抽象的描述。注意,有 File 对象,并不代表真实存在该⽂件
1.构造方法
签名 | 说明 |
File(File parent, String child)
|
根据⽗⽬录 + 孩⼦⽂件路径,创建⼀个新的 File 实例
|
File(String pathname)
|
根据⽂件路径创建⼀个新的 File 实例,路径可以是绝
对路径或者相对路径
|
File(String parent, String child)
|
根据⽗⽬录 + 孩⼦⽂件路径,创建⼀个新的 File 实
例,⽗⽬录⽤路径表⽰
|
注意!这里创建的是实例,并不代表一定有这个文件存在
2.方法
2.1 方法表
修饰符及返回值类型
|
⽅法签名
|
说明
|
String
|
getParent()
|
返回 File 对象的⽗⽬录⽂件路径
|
String |
getName()
|
返回 FIle 对象的纯⽂件名称
|
String |
getPath()
|
返回 File 对象的⽂件路径
|
String |
getAbsolutePath()
|
返回 File 对象的绝对路径
|
String |
getCanonicalPath()
|
返回 File 对象的修饰过的绝对路径
|
boolean
|
exists()
|
判断 File 对象描述的⽂件是否真实
存在
|
boolean
|
isDirectory()
|
判断 File 对象代表的⽂件是否是⼀
个⽬录
|
boolean
|
isFile()
|
判断 File 对象代表的⽂件是否是⼀
个普通⽂件
|
boolean
|
createNewFile()
|
根据 File 对象,⾃动创建⼀个空⽂
件。成功创建后返回 true
|
boolean
|
delete()
|
根据 File 对象,删除该⽂件。成功
删除后返回 true
|
void
|
deleteOnExit()
|
根据 File 对象,标注⽂件将被删
除,删除动作会到 JVM 运⾏结束时
才会进⾏
|
String[]
|
list()
|
返回 File 对象代表的⽬录下的所有
⽂件名
|
File[]
|
listFiles()
|
返回File 对象代表的⽬录下的所有
⽂件,以 File 对象表⽰
|
boolean
|
mkdir()
|
创建 File 对象代表的⽬录
|
boolean
|
mkdirs()
|
创建 File 对象代表的⽬录,如果必
要,会创建中间⽬录
|
boolean
|
renameTo(File dest)
|
进⾏⽂件改名,也可以视为我们平
时的剪切、粘贴操作
|
2.2 get相关的方法和构造方法
public static void main(String[] args) throws IOException {
//打开我们工程目录下的文件
File file = new File("E:\\Code\\lear-java\\io\\src");
//或者可以使用:
// File file = new File("E:/Code/lear-java/io");
//返回File对象的父目录文件路径
System.out.println(file.getParent());
//返回File对象的纯文件名称
System.out.println(file.getName());
//返回文件路径
System.out.println(file.getPath());
//返回File对象的绝对路径
System.out.println(file.getAbsolutePath());
//返回File修饰过的路径
System.out.println(file.getCanonicalPath());
}
这样看后面三种没有区别,在使用相对地址的时候才可以区分出
2.2.1 “.” 和 “..”
- . :表示当前目录
- .. : 表示上一级目录 或者 表示父级目录
public static void main(String[] args) throws IOException { //打开当前目录 File file = new File("./Test.txt"); //返回File对象的父目录文件路径 System.out.println(file.getParent()); //返回File对象的纯文件名称 System.out.println(file.getName()); //返回文件路径 System.out.println(file.getPath()); //返回File对象的绝对路径 System.out.println(file.getAbsolutePath()); //返回File修饰过的路径 System.out.println(file.getCanonicalPath()); }
那么当前目录又是哪里?
程序默认在idea工程中运行的,那么当前文件就是在当前工程目录下。
例如:我的工程名为《IO》那么就在IO工程下:
结果:
而且我们的Test.txt文件根本就不存在,所以就算你获取到了路径、名字等等,也不能代表文件是真实存在的。
public static void main(String[] args) throws IOException { //父级目录 File file = new File("../Test.txt"); //返回File对象的父目录文件路径 System.out.println(file.getParent()); //返回File对象的纯文件名称 System.out.println(file.getName()); //返回文件路径 System.out.println(file.getPath()); //返回File对象的绝对路径 System.out.println(file.getAbsolutePath()); //返回File修饰过的绝对路径 System.out.println(file.getCanonicalPath()); }
结果:
2.3 is相关的方法
当文件不存在:
public static void main(String[] args) { //此时这个文件并不存在 File file = new File("./Test.txt"); //判断文件是真实否存在 System.out.println(file.exists()); //判断文件是否是一个目录 System.out.println(file.isDirectory()); //判断文件是否是一个普通文件 System.out.println(file.isFile()); }
结果
此时我们加上创建文件的方法:
public static void main(String[] args) throws IOException { //此时这个文件并不存在 File file = new File("./Test.txt"); //创建文件 System.out.println(file.createNewFile()); //判断文件是真实否存在 System.out.println(file.exists()); //判断文件是否是一个目录 System.out.println(file.isDirectory()); //判断文件是否是一个普通文件,二进制文件也算是普通文件 System.out.println(file.isFile()); }
结果:
- 当文件存在,不会创建,返回false
- 当文件不存在,创建文件,返回true
- 创建失败抛出异常IOException
什么情况下会创建失败?
1. 硬盘空间不够了
在日常我们使用的电脑,一般来说不容易遇到这个问题,但是在以后的工作中可能就比较常见,因为每天都会产生大量的日志,会产生新的文件,这个时候就会创建失败抛出异常IOException。
2.没有权限
主要是读和写的权限,如果你只要读的权限去写,肯定也是不行的
3.父目录不存在
写入的目录路径错误,根本就没有这个目录
4.其他I/O错误
如文件路径被占用、硬件故障……
2.4 删除相关的方法
public static void main(String[] args) throws IOException {
File file = new File("./Test.txt");
//创建文件
System.out.println(file.createNewFile());
//删除文件,删除成功返回true
System.out.println(file.delete());
}
结果:
除了马上删除,还可以让文件在线程结束的时候删除:
public static void main(String[] args) throws IOException { File file = new File("./Test.txt"); //先创建文件 System.out.println(file.createNewFile()); //当进程结束的时候才会删除文件 file.deleteOnExit(); System.out.println("是否要结束进程"); //利用Scanner造成阻塞 Scanner sc = new Scanner(System.in); sc.next(); System.out.println("进程结束"); }
使用场景之一:
我们在word创建一个文档且还未保存的时候,它会创建一个临时文件用来保存,你正在编辑的内容。如果程序异常关闭了,就可以通过这个文件恢复之前编辑的内容。
2.5 返回当前目录下的文件
public static void main(String[] args) { File file = new File("."); String[] s = file.list(); for (int i = 0; i < s.length; i++) { System.out.println(s[i]); } }
结果:
打印出来的顺序并不和我们文件中看到的顺序相同
这个方法功能比较有限,我们一般使用listFiles()方法比较多,可以直接获取到目录下每个文件的实例
public static void main(String[] args) { File file = new File("."); //获取该文件下,所有对象的实例 File[] fs = file.listFiles(); for (int i = 0; i < fs.length; i++) { System.out.println(fs[i].getName()); } }
我们也可以通过这个方法,打印出当前目录下文件的名字。
但是!我们src也是一个目录,目录下的文件并不能通过这样的方式简单打印出来,
就需要使用递归的方法。在此之前我们需要知道这个方法的几种情况:
- 当遇到的文件不是目录,返回null
- 路径错误,返回null
- 如果是一个空目录,就返回一个空数组
2.5.1 递归实现遍历路径下所有文件
我们现在可以来实现一下:
private static void scan(File file) throws IOException { //如果不是目录,就退出 if (!file.isDirectory()) { return; } //是目录就获取,目录中每个文件的对象 File[] fs = file.listFiles(); // 如果是空目录 或者 不存在的路径 if (fs.length == 0 || fs == null) { return; } for (int i = 0; i < fs.length; i++) { //如果是普通文件就打印 if (fs[i].isFile()) { System.out.println(fs[i].getCanonicalPath()); }else { //到这里肯定就是目录了 //就先打印目录名,再继续递归 System.out.println(fs[i].getName()); scan(fs[i]); } } } public static void main(String[] args) { File file = new File("."); try { scan(file); } catch (IOException e) { e.printStackTrace(); } }
结果:
2.6 创建目录
public static void main(String[] args) {
File file = new File("./aaa");
//创建一个目录,如果创建成功返回true,否则false
boolean ok = file.mkdir();
System.out.println(ok);
}
结果:
那如果在有这个目录的情况下,我们再次运行,会创建成功吗?
直接返回了false创建失败了,说明它会先检查一下是否有同名的文件夹。
那么我们是否能同时创建,多级目录呢?
public static void main(String[] args) { File file = new File("./bb/cc/dd"); //创建一个目录,如果创建成功返回true,否则false boolean ok = file.mkdir(); System.out.println(ok); }
结果:
并不能同时创建多级目录,但是我们还有一个方法是可以创建多级目录的:
public static void main(String[] args) { File file = new File("./bb/cc/dd"); //创建一个目录,如果有必要会创建多级目录,如果创建成功返回true,否则false boolean ok = file.mkdirs(); System.out.println(ok); }
结果:
那么用mkdirs()方法就可以创建多级目录了
2.7 renameTo()
改名操作:
public static void main(String[] args) {
File file1 = new File("./Test.txt");
File file2 = new File("./Test2.txt");
//改名或者移动成功返回true,反之false
boolean ok = file1.renameTo(file2);
System.out.println(ok);
}
结果:
移动操作,需要最后名字写为相同的:
public static void main(String[] args) {
//现在我们想将,之前改名的Test2.txt,移动到之前创建的aaa文件夹中
File file1 = new File("./Test2.txt");
File file2 = new File("./aaa/Test2.txt");
//改名或者移动成功返回true,反之false
boolean ok = file1.renameTo(file2);
System.out.println(ok);
}
结果:
三、流 (Stream)
1.初时“流”
关于文件的操作,都是系统提供了API,java已经帮我们封装好了。
我们把这些封装好的类统称为,《文件流》或者《IO流》
流是一个形象的比喻,例如:
水流:
通过水龙头,接100ml水
- 直接一口气100ml接完
- 一次接50ml,分两次接完
- 一次接10ml,分10次接完
- 一次接1ml,分100次接完
- ……
文件流/IO流:
我要从文件读取100字节的数据
- 直接一口气,把100字节的数据读完
- 一次读50字节,分两次
- 一次读10字节,分10次
- 一次读1字节,分100次
- ……
java实现IO流的类有很多,分成两个大类:
1. 字节流(二进制):读写数据的基本单位,就是字节,实现的主要接口:
- InputStream(输入)
- OutputStream(输出)
2. 字符流:读写数据的基本单位,就是字符
- Writer(输入)
- Reader(输出)
上述四个类,都是“抽象类”,真正干活的是下面的一堆继承它的类。
实现了这么多类,怎么区分字符流和IO流呢?
凡是后缀带有 InputStream 或者 OutputStream 就是字节流
凡是后缀带有 Wirte 或者 Reader
我们只需要去了解常用的类就可以了。
2.输入输出的角度
我们从CPU的角度出发:
- 去硬盘上写数据,就是写
- 去硬盘上读数据,就是读
3.字节流的操作
3.1 InputStream
常用的方法
修饰符及返回值类型 方法签名 说明 int read() 读取一个字节的数据,返回-1代表已经读完了 int read(byte[] b) 最多读取b.length字节的数据到b中, 返回实际读到的数量;-1代表以及读完了 int read(byte[] b,int off,int len) 将从文件读取的数据放入到数组b中,off:代表开始存储数组的下标 ,len:代表最多读取的字节个数。返回读取到的字节个数,如果读到末尾返回-1 void close() 关闭字节流 InputStream 只是⼀个抽象类,要使⽤还需要具体的实现类。关于 InputStream 的实现类有很多,基本可以认为不同的输⼊设备都可以对应⼀个 InputStream 类,我们现在只关⼼从⽂件中读取,所以使⽤ FileInputStream
3.1.1 FileInputStream
public static void main(String[] args) throws FileNotFoundException { //打开字节流并,打开文件 这个地址可以是不存在的 InputStream inputStream = new FileInputStream("./Test.txt"); }
这样的一段代码,它会抛这样一个异常:FileNotFoundException,表示程序尝试打开或访问指定路径的文件时失败。它是IOExciption的一种特殊情况。
如果没有这个文件,就会抛出此异常:
这个语句还隐含了“打开文件”这样的操作,既然有打开就有关闭。
public static void main(String[] args) throws IOException { InputStream inputStream = new FileInputStream("./Test.txt"); //关闭字节流 inputStream.close(); }
如果不关闭,可能会造成程序崩溃:
打开文件,其实是在该进程中的 “文件描述符” 中,创建了一个新的表项。
文件描述符:描述了该进程都要操作哪些文件。数组的每个元素就是一个struct file对象,每个结构体就描述了对应的文件信息,数组的小标就称为“文件描述符”。
每次打开一个文件,就想当于在数组上占用了一个位置,而这个数组又是不能扩容的,如果数组满了就会打开文件失败。除非主动调用close才会关闭文件,或者这个进程直接结束了这个数组也被带走了。
但是难免会存在一些情况导致close()执行不到,例如:在执行到之前抛出了异常、遇到了return。所以我们应该把close()方法放到finally中:
public static void main(String[] args) { InputStream inputStream = null; try { inputStream = new FileInputStream("./Test.txt"); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { try { //关闭 inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
但是这样写感觉非常的繁琐,那我们其实还有一种解决方式:
public static void main(String[] args) { //写在这里,就不用加close()了。只有实现了Closeable的语句才可以写在这里 try (InputStream inputStream = new FileInputStream("./Test.txt")) { } catch (IOException e) { }
写在try()里的必须是实现了Closeable接口的,这样的写法是非常推荐的:
try()里支持放入多个对象,使用“;”分割
3.1.1.1 read()
每次读取一个字节的内容,读值只会返回0 - 255的数据,如果读取到了末尾返回-1。
创建一个文件Test.txt在里面写上hello:
public static void main(String[] args) { try (InputStream inputStream = new FileInputStream("./Test.txt")) { while (true) { int b = inputStream.read(); if (b == -1) { //如果等于-1就代表读到了文件末尾 return; } System.out.printf("0x%x = %c \n",b,b); } } catch (IOException e) { e.printStackTrace(); } }
结果:
一次读取太慢了,而且每去读一次,就会执行一次IO操作,IO操作的开销是比较大的,我们可以使用数组参数的read()。此方法虽然还是一个个在读到数组中的,但提升比较大的原因是节省了每次的IO操作开销。
3.1.1.2 read(byte[] b)
将读取到的参数,传入到数组b中。如果没有读取到末尾,就返回每次读取到的字节数,到了末尾返回-1
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("./Test.txt")) {
//防止数据一次读不完
while (true) {
byte[] buffer = new byte[1024];
//n代表读取到了多少个字节,读取到末尾仍然返回-1
int n = inputStream.read(buffer);
if (n == -1) {
//如果等于-1就代表读到了文件末尾
return;
}
for (int i = 0; i < n; i++) {
System.out.printf("0x%x -> %c\n",buffer[i],buffer[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
3.1.1.3 read(byte[] b,int off,int len)
将从文件读取的数据放入到数组b中,off:代表开始存储数组的下标 ,len:代表最多读取的字节个数。返回读取到的字节个数,如果读到末尾返回-1
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("./Test.txt")) {
byte[] buffer = new byte[1024];
//n代表读取到了多少个字节,读取到末尾仍然返回-1
//off:代表偏移量,此处写的1,就是从数组为1的位置开始写
//len:表示最多读取的字节个数
int n = inputStream.read(buffer,1,3);
if (n == -1) {
//如果等于-1就代表读到了文件末尾
return;
}
for (int i = 1; i < n+1; i++) {
System.out.printf("0x%x -> %c\n",buffer[i],buffer[i]);
}
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
3.1.1.4 Scanner获取文件内容
Scanner sc = new Scanner(System.in);
在这之前,我们可能一直不知道System.in是什么参数,我们点进去看一下:
public static final InputStream in = null;
我们发现他就是一个InputStream,那我们传入的参数就可以是InputStream对象。
我们现在文件中是这样的内容:
public static void main(String[] args) { try (InputStream inputStream = new FileInputStream("./Test.txt")) { Scanner sc = new Scanner(inputStream); //判断下一个是否为Int类型 while (sc.hasNextInt()) { System.out.println(sc.nextInt()); } } catch (IOException e) { e.printStackTrace(); } }
结果:
如果里面的内容被其他类型则会中断:
public static void main(String[] args) { try (InputStream inputStream = new FileInputStream("./Test.txt")) { Scanner sc = new Scanner(inputStream); //判断下一个是否为Int类型 while (sc.hasNextInt()) { System.out.println(sc.nextInt()); } } catch (IOException e) { e.printStackTrace(); } }
结果:
这个时候用字符串类型可能就比较好
如果是字符串
public static void main(String[] args) { try (InputStream inputStream = new FileInputStream("./Test.txt")) { Scanner sc = new Scanner(inputStream); //判断下一个是否为String类型 while (sc.hasNext()) { System.out.println(sc.next()); } } catch (IOException e) { e.printStackTrace(); } }
结果:
3.2 OutputStream
修饰符及返回值类型
|
⽅法签名
|
说明
|
void
|
write(int b)
|
写⼊要给字节的数据
|
void
|
write(byte[] b)
|
将 b 这个字符数组中的数据全部写
⼊ os 中
|
int
|
write(byte[] b, int off, int len)
|
将 b 这个字符数组中从 off 开始的
数据写⼊ os 中,⼀共写 len 个
|
OutputStream 同样只是⼀个抽象类,要使⽤还需要具体的实现类。我们现在还是只关⼼写⼊⽂件中,所以使⽤ FileOutputStream
3.2.1 FileOutputStream
依然包含打开文件这样的隐藏操作,所以也需要关闭字节流。打开失败依然会抛出FileNotFoundException。如果文件不存在会自动创建文件,但是如果目录都不存在会抛出异常
3.2.1.1 write(int b)
每次输入一个字节b,如果写入失败会抛出IOException
现在将Test.txt文件里的内容清空
public static void main(String[] args) { //写文件,依然包含打开文件这样的隐藏操作,打开失败依然会抛出FileNotFoundException try (OutputStream outputStream = new FileOutputStream("./Test.txt")) { //每次写入一个字节 outputStream.write(0x68);//h outputStream.write(0x65);//e outputStream.write(0x6c);//l outputStream.write(0x6c);//l outputStream.write(0x6f);//o } catch (IOException e) { e.printStackTrace(); } }
结果:
但是如果我们再次运行:
在我们创建这个对象的时候它回清空文件中的数据,再开始写。
那如果我们不想让它清空数据,就在实例化对象的时候填入一个true:
OutputStream outputStream = new FileOutputStream("./Test.txt",true)
这个时候就不会清空,文件中的数据了
3.2.1.2 write(byte[] b)
将数组b的内容写到文件中,如果写入失败会抛出IOException
我们用此方法写“你好”:
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("./Test.txt",true)) {
//此数组中存放的是,“你好”的二进制数据
//必须进行强制类型转换,不然会报错
byte[] buffer = {(byte) 0xe4,(byte) 0xbd,(byte) 0xa0,(byte) 0xe5,(byte) 0xa5,(byte) 0xbd};
outputStream.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
3.2.1.3 write(byte[] b, int off, int len)
b数组做为数据源,表示要写入的内容,
off:代表从b数组中哪一个元素开始写入
len:表示最多写入多少个字节
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("./Test.txt",true)) {
byte[] buffer = {(byte) 0xe4,(byte) 0xbd,(byte) 0xa0,(byte) 0xe5,(byte) 0xa5,(byte) 0xbd};
//将buffer数组的数据,从0下标开始,输入6个字节的数据
outputStream.write(buffer,0,6);
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
这个时候如果我们乱输入一个数据范围:
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("./Test.txt",true)) {
byte[] buffer = {(byte) 0xe4,(byte) 0xbd,(byte) 0xa0,(byte) 0xe5,(byte) 0xa5,(byte) 0xbd};
outputStream.write(buffer,1,3);
} catch (IOException e) {
e.printStackTrace();
}
}
结果:这个适合它就不认识你写的什么东西了
如果超出了你数据源的范围:
public static void main(String[] args) { try (OutputStream outputStream = new FileOutputStream("./Test.txt",true)) { byte[] buffer = {(byte) 0xe4,(byte) 0xbd,(byte) 0xa0,(byte) 0xe5,(byte) 0xa5,(byte) 0xbd}; //从 2 下标开始,一共就只有4个数据了,超过了这个就会抛出异常 outputStream.write(buffer,2,6); } catch (IOException e) { e.printStackTrace(); } }
如果是文本文件的操作,更适合字符流的方式
4.字符流的操作
4.1 Reader
修饰符及返回值类型 | 方法签名 | 说明 |
int | read() | 读取一个字符的数据,返回读取到的数据,但是是int类型,为了兼容读取到末尾返回-1,正常读取到数据的返回范围是0-65535。 |
int | read(char[] cbuf) | 将文件中读取到的字符,写到cbuf数组中。返回的是读取到字符的个数,如果读取到文件末尾,则返回-1 |
int | read(char[] cbuf, int off, int len) | 将在文件读取到的内容返回到cbuf数组中,off:从数组中的哪一个下标开始写,len表示最多读取的字符个数。返回读取到的个数,如果是读取到文件末尾就返回-1 |
4.1.1 FileReader
这个类和上面讲述的InputStream差不多,我就不过多讲述了
4.1.1.1 read()
读取一个字符的数据,返回读取到的数据,但是是int类型,为了兼容读取到末尾返回-1,正常读取到数据的返回范围是0-65535。如果文件不存在会自动创建文件,但是如果目录都不存在会抛出异常
public static void main(String[] args) {
try (Reader reader = new FileReader("./Test.txt")) {
while (true) {
//读取一个字符的内容,但是返回的是int类型,为了兼容-1
int c = reader.read();
if (c == -1) {
//读取到了文件末尾
return;
}
char ch = (char) c;
System.out.println(ch);
}
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
可能熟悉二进制范围的朋友就有疑问了,utf8汉字存储用了3个字节,java中的char类型是2给字节,怎么能装得下的。其实java中char类型进行了转码,转为了Unicode编码。
4.1.1.2 read(char[] cbuf)
将文件中读取到的字符,写到cbuf数组中。返回的是读取到字符的个数,如果读取到文件末尾,则返回-1
public static void main(String[] args) {
try (Reader reader = new FileReader("./Test.txt")) {
while (true) {
char[] buffer = new char[1024];
//返回读取到字符的个数,如果读到文件末尾返回-1
int n = reader.read(buffer);
if (n == -1) {
return;
}
for (int i = 0; i < n; i++) {
System.out.println(buffer[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
4.1.1.3 read(char[] cbuf,int off,int len)
将在文件读取到的内容返回到cbuf数组中,off:从数组中的哪一个下标开始写,len表示最多读取的字符个数。返回读取到的个数,如果是读取到文件末尾就返回-1
public static void main(String[] args) {
try (Reader reader = new FileReader("./Test.txt")) {
char[] buffer = new char[1024];
//返回读取到字符的个数,如果读到文件末尾返回-1
int n = reader.read(buffer,1,3);
if (n == -1) {
return;
}
for (int i = 1; i < n+1; i++) {
System.out.println(buffer[i]);
}
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
4.2 Writer
修饰符及返回值类型 | 方法签名 | 说明 |
void | write(String str) | 将str中的数据写入文件中 |
void | write(char cbuf[]) | 将cbuf数组中的值,写入到文件中 |
void | write(char cbuf[], int off,int len) | 将cbuf数组做为数据源,off(偏移量)做为从哪个数组下标开始写,len表示最多写多少个字符。 |
4.2.1FileWriter
这个类如果不在形参中写true,每次实例化对象的时候也会把文件清空
4.2.1.1 write(String str)
这个方法可以用Stirng传入就非常方便了
public static void main(String[] args) {
try (Writer writer = new FileWriter("./Test.txt")) {
writer.write("你好世界");
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
把之前的两个你好给清空了
当我们加上true:
public static void main(String[] args) { try (Writer writer = new FileWriter("./Test.txt",true)) { writer.write("你好世界"); } catch (IOException e) { e.printStackTrace(); } }
4.2.1.2 write(char cbuf[])
将cbuf数组中的值,写入到文件中
public static void main(String[] args) {
try (Writer writer = new FileWriter("./Test.txt",true)) {
char[] buffer = {'哈','哈','哈'};
writer.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
4.2.1.3 write(char cbuf[], int off,int len)
将cbuf数组做为数据源,off(偏移量)做为从哪个数组下标开始写,len表示最多写多少个字符。
public static void main(String[] args) {
try (Writer writer = new FileWriter("./Test.txt",true)) {
char[] buffer = {'啊','啊','啊','啊','啊','啊'};
writer.write(buffer,1,4);
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
四、案例练习
1.扫描指定⽬录,并找到名称中包含指定字符的所有普通⽂件(不包含⽬录),并且后续询问⽤⼾是否要删除该⽂件
public class demo4 {
//扫描指定⽬录,并找到名称中包含指定字符的所有普通⽂件(不包含⽬录),并且后续询问⽤⼾是否
//要删除该⽂件
public static void main(String[] args) {
//1.指定目录
System.out.println("请输入你要查找的目录");
Scanner sc = new Scanner(System.in);
String rootPath = sc.next();
File file = new File(rootPath);
//2.判断地址是否为一个有效地址
if (!file.isDirectory()) {
//不是一个目录
System.out.println("输入路径错位");
return;
}
//3.获取要查找的关键字
System.out.println("请输入你要查找的关键字");
String key = sc.next();;
//4.开始查找
scan(file,key);
}
private static void scan(File file, String key) {
//1.不是目录就不能继续递归了
if (!file.isDirectory()) {
return;
}
//2.获取当前目录下所有文件的对象
File[] fs = file.listFiles();
//3.防止发生IO错误 和 如果是一个目录
if (fs == null || fs.length == 0) {
return;
}
//4. 开始遍历
for (File f : fs) {
//5.如果是普通文件,判断是否有key值
if (f.isFile()) {
//7. 判断是否有key值
toDelete(f,key);
}else {
//6.如果不是普通文件继续递归
scan(f,key);
}
}
}
private static void toDelete(File f, String key) {
Scanner sc = new Scanner(System.in);
//获取文件名称是否包含key值
if (f.getName().contains(key)) {
//提示用户是否删除
System.out.println("是否删除" + f.getName() + "文件");
System.out.println("是Y/y 否N/n");
String ch = sc.next();
if (ch.equals("Y") || ch.equals("y")) {
f.delete();
}
}
}
}
要删除的目标:
输入的过程:
那么现在我们来看一下是否删除成功了:
2.复制普通文件
public class demo5 {
//复制普通文件
//读取二进制,并写入到目标文件
public static void main(String[] args) {
//1.输入要复制的文件
Scanner sc = new Scanner(System.in);
System.out.println("请输入你要复制的文件路径");
String sourcePath = sc.next();
File file = new File(sourcePath);
//2.如果不是普通文件
if (!file.isFile()) {
System.out.println("你输入的文件路径有误");
return;
}
//3.输入要复制的文件路径
System.out.println("请输入要粘贴的文件路径");
String targetPath = sc.next();
File destFile = new File(targetPath);
//4.目标文件的路径必须存在
if (!destFile.getParentFile().isDirectory()) {
System.out.println("目标文件的路径有误");
return;
}
//5.开始复制
//如果文件不存在,FileInputStream会自动创建文件
try (InputStream inputStream = new FileInputStream(sourcePath);
OutputStream outputStream = new FileOutputStream(targetPath)) {
while (true) {
byte[] buffer = new byte[1024];
int n = inputStream.read(buffer);
if (n == -1) {
break;
}
//数据源不是每一次都能装满1024个字节
outputStream.write(buffer,0,n);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果:
3.扫描指定⽬录,并找到内容中包含指定字符的所有普通⽂件(不包含⽬录)
public class demo6 {
//扫描指定⽬录,并找到内容中包含指定字符的所有普通⽂件(不包含⽬录)
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//1.输入要查找的目录
System.out.println("请输入你要查找的目录");
String path = sc.next();
File file = new File(path);
//2.如果不是目录
if (!file.isDirectory()) {
System.out.println("你输入的路径有误");
return;
}
//3.输入查找词
System.out.println("请输入你要查找的内容");
String key = sc.next();
//4.开始查找
scan(file,key);
}
//5.开始递归遍历
private static void scan(File file, String key) {
//如果不是目录就不能递归了
if (!file.isDirectory()) {
return;
}
File[] fs = file.listFiles();
if (fs.length == 0 || fs == null) {
return;
}
for (File f: fs) {
//如果是普通文件进行处理判断
if (f.isFile()) {
search(f,key);
} else {
//不是普通文件继续遍历
scan(f,key);
}
}
}
//6.进行内容查询判断
private static void search(File f, String key) {
//用这个容器来接收所有数据
StringBuilder stringBuilder = new StringBuilder();
try (Reader reader = new FileReader(f)) {
char[] buffer = new char[1024];
while (true) {
int n = reader.read(buffer);
if (n == -1) {
break;
}
//将buffer0下标的数据,读取n个.并以String类型返回
String s = new String(buffer,0,n);
//放入容器,将字符串拼接起来
stringBuilder.append(s);
}
} catch (IOException e) {
e.printStackTrace();
}
//判断容器中是否有key值
if (stringBuilder.indexOf(key) == -1) {
//文件中没有要查询的值
return;
}
//到这里肯定就是有key值了
System.out.println("找到了匹配的文件 " + f.getAbsolutePath());
}
}
我新建了个文件夹,里面存放了一些文本文件,内容如下:
结果: