摄影分享~
文章目录
- 文件
- 文件路径(Path)
- 文件的类型
- Java中操作文件
- File概述
- 文件内容的读写——数据流
- 字节流
- InputStream概述
- OutputStream 概述
- 字符流
- FileInputStream 概述
- 利用 Scanner 进行字符读取
- 实例练习
文件
文件:File
这个概念,在计算机里也是一次多用:
狭义的文件:指的是硬盘上的文件和目录。针对硬盘这种持久化存储的I/O设备,当我们想要进行数据保存时,往往不是保存成一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概念,就类似办公桌上的一份份真实的文件一般。
广义的文件:泛指计算机中的很多的软硬件资源.
同时,随着文件越来越多,对文件的系统管理也被提上了日程,如何进行文件的组织呢,一种合乎自然的想法出现了,就是按照层级结构进行组织 —— 也就是我们数据结构中学习过的树形结构。这样,一种专门用来存放管理信息的特殊文件诞生了,也就是我们平时所谓文件夹(folder)或者目录(directory)的概念。
文件路径(Path)
每个文件,在硬盘上都有一个具体的路径。
例如:上述照片的路径就是:E:/rocket.jpg
表示一个文件的具体位置路径,就可以使用 / 来分割不同的目录级别。
就可以表示为:E:\tmp\111\aaa
在Windows中/
和\
都可以来做分隔符。但是在平常的代码中更建议使用/
,因为/
不存在字符转义问题。
在路径这里,有两种表示路径的风格。
- 绝对路径,以
c: d:
盘符开头的路径 - 相对路径,以当前所在的目录为基准,以
.
或者..
开头(有时可以省略),找到指定的路径
当前所在目录:称为工作目录,每个程序运行的时候,都有一个工作目录。
定位到111这个目录,是E:/tmp
如果工作目录不同,定位到同一个文件,相对路径写法是不同的:
例如:定位到111
如果工作目录是E:/
相对路径写作:./tmp/111
如果工作目录是E:/tmp
相对路径写作:./111
如果工作目录是E:/tmp/222
相对路径写作:../111
如果工作目录是E:/tmp/222/bbb
相对路径写作:../../111
..
表示上一级路径
文件的类型
文件的类型有很多种,比如:word,exe,图片,视频,pdf,源代码,动态库等。这些文件可以归纳到两类中:
- 文本文件(存的是文本,字符串)
字符串,是由字符构成的,每个字符,都是通过一个数字来表示的。这个文本文件里存的数据,一定是合法的字符,都是指定字符编码的码表之内的数据。 - 二进制文件(存的是二进制,不一定是字符串)
没有任何限制
如何判断一个文件是二进制还是文本文件?
使用记事本打开,如果乱码就是二进制文件;如果没有乱码,就是文本。
Java中操作文件
主要分为两类:
- 针对文件系统操作。(文件的创建,删除,重命名)
- 针对文件内容操作。(文件的读和写)
Java 中通过 java.io.File
类来对一个文件(包括目录)进行抽象的描述。注意,有 File 对象,并不
代表真实存在该文件。
File概述
常见属性
修饰符及类型 | 属性 | 说明 |
---|---|---|
static String | pathSeparator | 依赖于系统的路径分隔符,String 类型的表示 |
static char | pathSeparator | 依赖于系统的路径分隔符,char 类型的表示 |
pathSeparator
(File中的一个静态变量)就是/
或者\
跟着系统走。
构造方法
签名 | 说明 |
---|---|
File(File parent, Stringchild) | 根据父目录 + 孩子文件路径,创建一个新的 File 实例 |
File(String pathname) | 根据文件路径创建一个新的 File 实例,路径可以是绝对路径或者相对路径 |
File(String parent, Stringchild) | 根据父目录 + 孩子文件路径,创建一个新的 File 实例,父目录用路径表示 |
方法
修饰符及返回值类型 | 方法签名 | 说明 |
---|---|---|
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(Filedest) | 进行文件改名,也可以视为我们平时的剪切、粘贴操作 |
boolean | canRead() | 判断用户是否对文件有可读权限 |
boolean | canWrite() | 判断用户是否对文件有可写权限 |
方法演示:
注意:
File file = new File("./test.txt");
中不要求E:/
这里真的有test.txt
如果想要可以自己手动创建:
throws IOException
这个异常在IO中是特别容易出现的异常。
deleteOnFile()
程序退出的时候,自动删除。(打开一个word文档,就会在同级目录下生成出一个临时文件,关闭word,这个文件就没有了)
这个临时文件,相当于保存了当前实时编辑的内容,防止编辑了很多东西之后突然停电,导致数据丢失。这种临时数据就使用deleteOnExit的方式来删除。
文件内容的读写——数据流
什么叫做流呢?
举个🌰:
水龙头,通过这个水龙头可以接水。
比如你要接100ml水,你可以一次接100ml,一次接完
也可以一次接50ml,分两次接
还可以一次接10ml,分10次接
…
这是水流~~
同理,从文件中读100个字节,就可以:
一次性读100字节,一次读完。
一次性读50字节,两次读完。
一次读10字节,十次读完。
…
因此就把读写文件的相关对象,称为“流对象”
Java标准库的流对象,从类型上分为两大类:
- 字节流:操作二进制数据的
InputStream
OutoutStream
FileInputSrteam
FileOutputStream
- 字符流:操作文本数据的
Reader
Writer
FileReader
FileWriter
这些类的使用方法是非常固定的,核心就是四个操作:
- 打开文件
- 关闭文件
- 读文件(read)=>针对
InputStream/Reader
- 写文件(writer)=>针对
OutputStream/Writer
注意InputStream
OutoutStream
Reader
Writer
都是抽象类,不能直接new
字节流
InputStream概述
可以看到read的三个版本:
方法:
修饰符及返回值类型 | 方法签名 | 说明 |
---|---|---|
int | read() | 一次读取一个字节,返回 -1 代表已经完全读完了 |
int | read(byte[] b) | 把读到的内容填充到参数的这个字节数组中(此处的参数是“输出型参数”)返回值是实际读取的字节数 |
int | read(byte[] b,int off, int len) | 最多读取 len - off 字节的数据到 b 中,放在从 off 开始,返回实际读到的数量;-1 代表以及读完了 |
void | close() | 关闭字节流 |
注意FileNotFoundExpection
是IOExpection
的一个子类。所以抛异常的时候只需要抛IOExpection
就够了。
read();
无参数版本详解:
可以看到打印出来是abcd的ascii值
如果文本中是中文“你好。”那么打印出来则为:
这里使用的utf8,utf8码表对应的16进制则为以上值。(utf8中一个汉字对应三个字节)
read(byte[])
版本详解:
此版本中需要调用者提前准备好一个数组buffer
(缓冲区)用来提高IO操作的效率。
int len = inputStream.read(buffer);
中传参操作,相当于是把刚才准备好的数组,交给read
方法,让read
方法内部针对这个数组进行填写。(此处参数相当于输出型参数)
注意read
的行为和返回值。read
会尽可能的把参数传进来的数组给填满。上面这里给出的数组长度时1024,read
就会尽可能的读取1024个字节,填到数组中。但实际上,文件剩余长度是有限的,如果剩余长度超过1024,此时1024个字节都会被填满,返回值就是1024了。如果当前剩余的长度不足1024,此时有多少就填多少。read方法就会返回当前实际读取的长度。
读完了,一个字节都没读到,则返回-1.
使用了InputStream来读文件,可以使用OutStream来写文件。
OutputStream 概述
修饰符及返回值类型 | 方法签名 | 说明 |
---|---|---|
void | write(int b) | 写入要给字节的数据 |
void | write(byte[]b) | 将 b 这个字符数组中的数据全部写入 os 中 |
int | write(byte[]b, int off,int len) | 将 b 这个字符数组中从 off 开始的数据写入 os 中,一共写 len 个 |
void | close() | 关闭字节流 |
void | flush() | 重要:我们知道 I/O 的速度是很慢的,所以,大多的 OutputStream 为了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的数据,很可能会遗留一部分在冲区中。需要在最后或者合适的位置,调用 flush(刷新)操作,将数据刷到设备中。 |
详解write(int b)
:
注意:在abcd之前记事本中的内容是“你好”。对于OutputStream
来说,默认情况下,打开一个文件,会先清空文件原有的内容。
如果不想清空,流对象还提供了一个“追加写”对象。通过这个可以实现不清空文件,把新内容追加到写后面。
注意:input
和output
的方向:是以CPU为中心,来看待这个方向的。
以CPU为中心:
流向CPU的方向,就是输入。所以就把数据从硬盘到内存这个过程称为读(input)
流出CPU的方向,就是输出。所以就把数据从内存到硬盘这个过程称为写(output)
详解close()
outputStream.close();
这里的close操作,含义是关闭文件。
进程->在内核中,使用PCB这样的数据来表示进程。
一个线程对应一个PCB。一个进程可以对应多个PCB。
PCB中有一个重要的属性:文件描述符表(相当于一个数组,记录了该进程打开了哪些文件)
一个进程里有多个线程多个PCB,这些PCB公用同一个文件描述符表。
每次打开文件操作,就会在文件描述符表中,申请一个位置。把这个信息放进去。每次关闭文件,也就会把这和文件描述符表对应的表项给释放。
那么,如果我们忘记写这个close();会怎么办呢?
如果没写close,对应的表项,没有及时释放。虽然Java有GC(垃圾回收机制),GC操作会在回收这个outputStream对象的时候去完成这个释放操作,但是这个GC回收器不一定及时。所以,如果不手动释放,意味着文件描述符表可能很快就被占满了。如果占满了之后,后面再次打开文件,就会打开失败。文件描述符表最大长度,不同系统上不太一样,基本是(几百到几千)
我们应该如何保证这个close会被执行到呢?
上述写法虽然没有显式的写close
,实际上是会执行的。只要try
语句块执行完毕,就可以自动执行到close
。
这个语法在Java中称为 try with resources
当然,要使用这个语法必须实现Closeable接口的类,才可以放到try的()中被自动关闭。
close操作,会触发缓冲区的刷新。(刷新操作,就是把缓冲里的内容写到硬盘里)
字符流
用法和字节流类似。
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class IODemo7 {
// 字符流的操作
public static void main(String[] args) {
try (Reader reader = new FileReader("e:/test.txt")) {
while (true) {
int ch = reader.read();
if (ch == -1) {
break;
}
System.out.println("" + (char)ch);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class IODemo9 {
public static void main(String[] args) {
try (Writer writer = new FileWriter("e:/test.txt")) {
writer.write("hello world");
// 手动刷新缓冲区
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileInputStream 概述
构造方法:
签名 | 说明 |
---|---|
FileInputStream(File file) | 利用 File 构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输入流 |
利用 Scanner 进行字符读取
构造方法 | 说明 |
---|---|
Scanner(InputStream is, String charset) | 使用 charset 字符集进行 is 的扫描读取 |
Scanner scanner = new Scanner(System.in);
System.in
就是一个输入流对象。
实例练习
- 扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件
import java.io.File;
import java.util.Scanner;
public class IODemo11 {
private 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("请输入要删除的文件名:");
String nameToDelete = scanner.next();
//针对指定的路径进行扫描,递归操作
//先从跟目录出发(root)
//先判定一下这个目录中是否包含我们需要删除的文件,如果是就删除
//否则就跳过,下一个
//如果当前这里包含了一些目录,再针对子目录进行递归
scanDir(root,nameToDelete);
}
private static void scanDir(File root, String nameToDelete) {
System.out.println("[scanDir]"+root.getAbsolutePath());//查看递归的查找路径
//1.先列出root下的文件和目录
File[] files = root.listFiles();
if(files == null){
//当前root目录下为空
//结束递归
return;
}
//2.遍历当前列出的结果
for (File f: files) {
if(f.isDirectory()){
//如果是目录就进一步递归
scanDir(f,nameToDelete);
}else{
//如果是普通文件,则判定是否要删除?
if(f.getName().contains(nameToDelete)){
System.out.println("是否确认删除"+f.getAbsolutePath());
String choic = scanner.next();
if(choic.equals("yes")||choic.equals("YES")){
f.delete();
System.out.println("删除成功!");
}else{
System.out.println("删除取消!");
}
}
}
}
}
}
- 进行普通文件的复制
把一个文件拷贝成另一个文件
import java.io.*;
import java.util.Scanner;
public class IODemo8 {
public static void main(String[] args) {
//输入两个路径
//源 和 目标
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);
}
}catch (IOException e){
e.printStackTrace();
}
}
}