目录
文件
文件概念
文件的路径
路径有俩种表示风格
文件类型
如何区分文本文件还是二进制文件?
java对文件的操作
File类中的一些方法
流对象
流对象的简单概念
java标准库的流对象
1.字节流,(操作二进制数据的)
2.字符流 (操作文本数据的)
流对象最核心的四个操作
注意,intput 和 output的方向
读文件(字节流)
read无参数版本
read一个参数版本
理解read的行为和返回值
写文件
关于close
如何确保close被执行到
字符流
读操作
写操作
关于Scanner
练习
用户输入一个查询的词,看看当前目录下(以及子目录里)是否有匹配的结果,如果有匹配结果,就询问用户是否删除
把一个文件拷贝成另一个文件(把第一个文件按照字节读取,把结果写入到另一个文件中)
文件
文件概念
狭义的文件:指的是硬盘上的 文件 和 目录(文件夹)
广义的文件:泛指计算机中的很多 软硬件资源
操作系统中,把很多的硬件设备和软件资源抽象成了文件,按照文件的方式来统一管理.
例如:在网络编程中的网卡,操作系统是把网卡当作一个文件来进行操作的
文件的路径
之前学习的代码中,存储数据,主要靠变量,变量是在内存中的
现在学习的文件,则是在硬盘上
每个文件,在硬盘上都有一个具体的路径
路径有俩种表示风格
1.绝对路径:以c:d:盘符开头的路径
2.相对路径:以当前所在目录为基准,以.或..开头(.有时可以省略),找到指定路径
当前所在目录:称为工作目录,每个程序运行的时候,都有一个工作目录(在控制台里通过命令操作较为明显)
假定,当前的工作目录是,d:/tmp
tmp目录下有111这个目录,定位到111这个目录,就可以表示成
./111(./就表示的是当前的目录 d:/tmp)
../表示当前目录的上级目录,如果工作目录是d:/tmp/222 想要定位到111这个目录,相对路径写作.
../111(..表示当前目录d:/tmp/222 的上级目录 d:/tmp)
文件类型
如何区分文本文件还是二进制文件?
直接使用 记事本 打开,如果乱码了,说明是二进制文件,如果没乱,就是文本文件
java对文件的操作
1.针对文件系统操作(文件的创建,删除,重命名)
2.针对文件内容操作(文件读和写)
java标准库提供了File这个类
在new File 对象的时候,构造方法参数中,可以指定一个路径.此时File对象代表这个路径对应的文件
File类中的一些方法
方法
代码
public static void main(String[] args) throws IOException {
File file = new File("./test.txt");
System.out.println(file.getName());
System.out.println(file.getParent());
System.out.println(file.getAbsoluteFile());
System.out.println(file.getPath());
System.out.println(file.getCanonicalFile());
}
运行结果
方法
代码
public static void main(String[] args) throws IOException {
File file = new File("D:/test.txt");
file.createNewFile();
System.out.println(file.exists());
System.out.println(file.isFile());
System.out.println(file.isDirectory());
}
运行结果
方法
代码
public static void main(String[] args) throws IOException {
File file = new File("D:/test.txt");
System.out.println(file.exists());
file.createNewFile();
System.out.println(file.exists());
file.delete();
System.out.println(file.exists());
}
运行结果
方法
代码
public static void main(String[] args) {
File file = new File("./test");
file.mkdir();
}
public static void main(String[] args) {
File file = new File("./test/aaa/bbb");
file.mkdirs();
}
运行结果
流对象
流对象的简单概念
针对文件内容,使用"流对象" 进行操作,从文件中读100个字节,我们可以一次读100个字节,一次读完
也可以一次读20个字节,5次读完,我们可以随心所欲的读
java标准库的流对象
从类型上分为俩大类
1.字节流,(操作二进制数据的)
InputStream FileInputStream
OutputStream FileOutputStream
2.字符流 (操作文本数据的)
Reader FileReader
Writer FileWriter
流对象最核心的四个操作
这些类的使用方式是固定的,核心就是四个操作
1.打开文件(构造对象)
2.读写文件(read) ==>针对的是 InputStream/Reader
3.写文件(writer) ==> 针对OutputStream/Writer
4.关闭文件(close)
注意,intput 和 output的方向
我们是以CPU为中心,来看待这个方向的
数据朝着CPU的方向流向,就是输入,所以把从硬盘中读取数据到内存 这个过程称为读 input
数据远离CPU的方向流向,就是输入,所以把从内存中写数据到硬盘 这个过程称为写 output
读文件(字节流)
read无参数版本
代码
public static void main(String[] args) throws IOException {
//创建 InputStream 对象的时候,使用绝对路径或者相对路径,都可以,也可以使用File对象
InputStream inputStream = new FileInputStream("D:/test.txt");
//进行读操作
while (true){
int b = inputStream.read();
if(b == -1){
break;
}
System.out.println(""+(byte)b);
}
inputStream.close();
}
运行结果
D盘中test的文件内容
read一个参数版本
public static void main(String[] args) throws IOException {
//创建 InputStream 对象的时候,使用绝对路径或者相对路径,都可以,也可以使用File对象
InputStream inputStream = new FileInputStream("D:/test.txt");
while(true){
byte[] buffer = new byte[1024];
int len = inputStream.read(buffer);
System.out.println("len "+len);
if(len == -1){
break;
}
for (int i = 0; i < len; i++) {
System.out.println(""+buffer[i]);
}
}
inputStream.close();
}
运行结果
理解read的行为和返回值
上面这里给的数组长度是1024,read就会尽可能读取1024个字节,填到数组里.但实际上,文件剩余长度是有限的,如果剩余长度超过1024,此时1024个字节就会填满,返回值是1024,如果当前剩余的长度不足1024,此时有多少就填多少,read方法就会返回当前实际读取的长度
第二个版本的代码有什么好处?
buffer存在的意义,是为了提高IO效率,单次IO操作,是要访问硬盘IO设备,单次操作是比较消耗时间的
如果频繁进行这样的IO操作,耗时比较大
单次IO时间是一定的,如果能缩短IO次数,此时就可以提高程序的整体效率了
第一个版本的代码,是一次读取一个字节,循环次数比较高,read次数很高,读取IO次数也很高
第二个版本的代码,是一次读取1024个字节,循环次数降低了很多,read次数变少了
写文件
代码
public static void main(String[] args) throws IOException {
OutputStream outputStream = new FileOutputStream("D:/test.txt")
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
outputStream.write(100);
outputStream.close();
}
运行结果
对于OutputStream 来说,默认情况下,打开一个文件,会先清空文件原有的内容(这样,之前的"hello"就被清空了)
如果不想清空,流对象还提供了一个"追加写"对象,通过这个就可以实现不清空文件,把新内容追加到后面.
关于close
对于上述代码中的outputStream.close();这里的close操作,含义是关闭文件
一个线程对于一个PCB,一个进程对应1个或多个PCB
PCB有一个重要的属性,文件描述符表(相当于一个数组),记录了该进程打开了哪些文件.(即使一个进程有多个线程多个PCB也没关系,这些PCB共用一个文件描述符表)
如果没有close,对应的表项,没有及时释放.虽然 java有GC,GC操作会在回收这个outputStream对象的时候完成释放操作,但是这个GC不一定及时...
所以,如果不能手动释放,意味着文件描述符表可能很快就被占满了(文件描述符表这个数组,不能自动扩容,有上限)
如果占满了后,再次打开文件,就会打开失败
close 一般来说是要执行的,但是如果一个程序,有一些文件对象自始至终都要使用,也可以不用关闭
随着进程结束,PCB销毁了,文件描述符表也就销毁了,对应的资源操作系统就自动回收了,因此,如果一个文件close之后,程序就立即结束了,此时也可以省略close
有时我们可能会发现,写文件的内容没有真正在文件中出现,很大可能是因为缓存区,
写操作其实是,先写到缓冲区里(缓冲区有很多种形态,自己写的代码里可以有缓冲区,标准库里也可以有缓冲区,操作系统内核里也可以有缓冲区)
写操作执行完了,内容可能在缓冲区,还没有真正进入硬盘,close操作 就会触发缓冲区的刷新(刷新操作就是把缓冲区的内容写到硬盘里)
除了close之外,还可以通过flush方法刷新缓冲区(此时文件不会立即关闭)
如何确保close被执行到
刚刚的代码可以改成这样
public static void main(String[] args) throws IOException {
try(OutputStream outputStream = new FileOutputStream("D:/test.txt")) {
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
outputStream.write(100);
}
}
这是更推荐的写法,这个写法虽然没有显示的写 close,实际上会执行的,只要try语句块执行完毕,就可以自动执行到close
这个语法,在java中被称为try with resources ,当然不是随便拿一个对象放在try()里就能自动释放,必须要这个对象实现了Closeable接口
实现了这个Closeable接口的类才可以放到try()中被自动关闭,这个接口提供的方法就是close方法
字符流
读操作
代码
public static void main(String[] args) throws IOException {
try(Reader reader = new FileReader("D:/test.txt")){
while(true){
int ch = reader.read();
if(ch == -1){
break;
}
System.out.println((char)ch+"");
}
}
}
运行结果
写操作
public static void main(String[] args) throws IOException {
try(Writer writer = new FileWriter("D:/test.txt")){
writer.write("hello world");
}
}
运行结果
关于Scanner
Scanner是搭配流对象来使用的
代码
public static void main(String[] args) throws IOException {
try(InputStream inputStream = new FileInputStream("D:test.txt")) {
//此时读取的内容就是从 文件 中读取了
Scanner scanner = new Scanner(inputStream);
scanner.next();
}
}
练习
用户输入一个查询的词,看看当前目录下(以及子目录里)是否有匹配的结果,如果有匹配结果,就询问用户是否删除
代码
import java.io.File;
import java.util.Scanner;
public class IODemo10 {
static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
//让用户搜索一个指定搜索的目录
System.out.println("请输入要搜索的路径: ");
String basePath = scanner.next();
//针对用户输入进行简单判定
File root = new File(basePath);
if(!root.isDirectory()){
//路径不存在,或者只是一个普通的文件,此时无法进行搜索
System.out.println("输入的目录有误!");
return;
}
//再让用户输入一个要删除的文件名
System.out.println("请输入要删除的文件");
//此处要使用next,而不能使用nextLine
String nameToDelete = scanner.next();
//针对指定的路径进行扫描,递归操作
//先从根目录出发
//判定一下,当前的这个目录里,是否包含我们所需要删除的目录.如果是则删除,否则跳过下一个
//如果当前目录里包含一些目录,再针对子目录进行递归
scanDir(root,nameToDelete);
}
private static void scanDir(File root, String nameToDelete) {
//1.先列出 root 下的文件和目录
File[] files = root.listFiles();
if(files == null){
//当前root 目录下没有东西,是一个空目录
//结束继续递归
return;
}
//2.遍历当前列出的结果
for(File x : files){
if(x.isDirectory()){
//如果是目录,就进一步递归
scanDir(x,nameToDelete);
}else{
//如果是普通文件,则判定是否要删除
if(x.getName().contains(nameToDelete)){
System.out.println("确实是否要删除: "+x.getAbsolutePath()+"嘛");
String choice = scanner.next();
if(choice.equals("y")||choice.equals("Y")){
x.delete();
System.out.println("删除成功");
}else{
System.out.println("删除取消");
}
}
}
}
}
}
运行结果
把一个文件拷贝成另一个文件(把第一个文件按照字节读取,把结果写入到另一个文件中)
代码
import java.io.*;
import java.util.Scanner;
public class IODemo11 {
public static void main(String[] args) throws IOException {
//输入俩个路径
//源 和 目标(从哪里,拷贝到哪里)
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要拷贝哪个文件: ");
String srcPath = scanner.next();
System.out.println("请输入要拷贝到哪个地方: ");
String destPath = scanner.next();
File srcFile = new File(srcPath);
if(!srcFile.isFile()){
//如果源路径不是一个文件(是一个目录,或者不存在)
//此时不做任何操作
System.out.println("输入的源路径有误");
return;
}
File destFile = new File(destPath);
if(destFile.isFile()){
//如果目标路径已经存在,认为不能拷贝
System.out.println("当前输入的目标路径有误");
return;
}
//进行拷贝操作
try(InputStream inputStream = new FileInputStream(srcFile);
OutputStream outputStream = new FileOutputStream(destFile)){
//进行读文件操作
while(true){
int b = inputStream.read();
if(b == -1){
break;
}
//进行写操作
outputStream.write(b);
}
}
}
}