目录
一、文件的定义
二、文件路径
三、文件的类型
四、JAVA中如何操作文件
4.1 在文件系统的层面上来操作文件
4.1.1 get相关方法
4.1.2 普通文件的创建和删除
4.1.3 观察deleteOnExit的现象
4.1.4 创建目录
4.1.5 文件重命名
4.2 操作文件的内容,即操作文件里保存的数据
4.2.1利用InputStream/FileInputStream读文件
4.2.2 利用Scanner进行字符读取
4.2.3 利用Reader/FileReader读取文件
4.2.4 利用OutputStreamWriter进行字符写入
五、示例
例子1:扫描指定目录,并找到包含指定字符的所有普通文件,并询问用户是否要删除该文件
例子2:普通文件的复制
例子3:扫描指定目录,找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
一、文件的定义
在一般情况下,我们说的文件都是指存储在硬盘里的数据(普通文件),但是实际上,文件的概念要更广泛一点,操作系统会将很多的硬件设备、软件资源等也给抽象成文件。举例:当学习网络编程时,读写网卡,这个时候操作网卡的代码和操作普通文件没啥区别,主要是因为操作系统把网卡这样的硬件设备也给抽象成文件了。
二、文件路径
绝对路径:以/开头或者以盘符开头,是指从树根出发,到该文件之间经历的路径
相对路径:以.或者..开头,从当前的工作目录出发,中间历经的路径
三、文件的类型
文本文件:文件里存的是文本,使用记事本能直接打开不乱码
二进制文件:文件里存的是二进制数据,使用记事本打开会出现乱码
在操作文件时,要先明确文件的类型,文本文件操作的基本单位是字符,二进制文件操作的基本单位是字节。
四、JAVA中如何操作文件
4.1 在文件系统的层面上来操作文件
创建文件、删除文件、创建目录、拷贝文件、重命名文件
JAVA中通过java.io.File类对一个文件、目录进行抽象的描述。注意:有File对象,并不代表真实存在该文件。
说法 | 说明 |
File(File parent,String child) | 根据父目录+孩子文件路径,创建一个新的File实例 |
File(String pathname) | 根据文件路径创建一个新的File实例,路径可以是绝对路径或者相对路径 |
File(String parent,String child) | 根据父目录+孩子文件路径创建一个新的File实例,父目录用路径表示 |
4.1.1 get相关方法
方法 | 说明 |
getParent() | 返回File对象的父目录文件路径 |
getName() | 返回File对象的纯文件名称 |
getPath() | 返回File对象的文件路径 |
getAbsolutePath() | 返回File对象的绝对路径 |
getCanonicalPath() | 返回File对象的修饰过的绝对路径 |
File f2 = new File("./a2.txt");
System.out.println(f2.getParent());
System.out.println(f2.getName());
System.out.println(f2.getPath());
System.out.println(f2.getAbsolutePath());
System.out.println(f2.getCanonicalPath());
4.1.2 普通文件的创建和删除
方法 | 说明 |
exists() | 判断File对象描述的文件是否真实存在 |
isDirectory() | 判断File对象代表的文件是否是一个目录 |
isFile() | 判断File对象代表的文件是否是一个普通文件 |
createNewFile() | 根据File对象,自动创建一个空文件。成功创建后返回true |
delete() | 根据File对象,删除该文件,成功删除后返回true |
File f3 = new File("./a3.txt");
System.out.println(f3.exists());
System.out.println(f3.isDirectory());
System.out.println(f3.isFile());
System.out.println("----------------");
System.out.println(f3.createNewFile());
System.out.println(f3.exists());
System.out.println(f3.isDirectory());
System.out.println(f3.isFile());
System.out.println(f3.delete());
System.out.println(f3.exists());
4.1.3 观察deleteOnExit的现象
方法 | 说明 |
deleteOnExit() | 根据File对象,标注文件将被删除,删除动作会到JVM运行结束时才会进行 |
File f4 = new File("./a4.txt");
System.out.println(f4.createNewFile());
f4.deleteOnExit();
// 通过Scanner构造一个阻塞,看看效果
Scanner scanner = new Scanner(System.in);
scanner.hasNext();
4.1.4 创建目录
方法 | 说明 |
mkdir() | 创建File对象代表的目录 |
mkdirs() | 创建File对象代表的目录,如果必要,会创建中间目录 |
File f5 = new File("./a/b/c/");
f5.mkdirs();
System.out.println(f5.isDirectory());
创建多级目录时中间目录不存在,mkdir()无法创建成功,mkdirs()可以创建成功。
4.1.5 文件重命名
方法 | 说明 |
renameTo(FileDest) | 进行文件改名,也可视为平时的文件剪切、粘贴操作 |
File f6 = new File("./aa.txt");
File f7 = new File("./aaa.txt");
f6.createNewFile();
System.out.println("f6.exists()"+f6.exists());
System.out.println("f7.exists()"+f7.exists());
f6.renameTo(f7);
System.out.println("f6.exists()"+f6.exists());
System.out.println("f7.exists()"+f7.exists());
实际上不是每次创建文件都会成功,最典型的失败就是没有权限,如果创建失败就会出现一个IOException这样的异常。delete也不是每次都会成功,一方面是权限,另一方面是文件路径不对
4.2 操作文件的内容,即操作文件里保存的数据
字节流:InputStream读,OutputStream写。针对二进制文本进行读写,基本单位是字节
字符流:Reader读,Writer写,针对文本文件进行读写,操作基本单位是字符
4.2.1利用InputStream/FileInputStream读文件
方法 | 说明 |
read() | 读取一个字节的数据。返回-1代表已经完全读完了 |
read(byte[] b) | 最多读取b.length字节的数据到b中,返回实际读到的数量。-1代表已经读完了 |
read(byte[] b,int off,int len) | 最多读取len-off字节的数据到b中,放在从off开始的位置,返回实际读到的数量,-1代表已经读完了 |
close() | 关闭字节流 |
InputStream只是一个抽象类,需要使用需要实现一个具体的实现类。关于InputStream的实现类有很多,由于现在只关心从文件中读取,所以使用FileInputStream。
方法 | 说明 |
FileInputStream(File file) | 利用File文件构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输入流 |
// InputStream是一个抽象类,是总领全局的,表示所有的按二进制读取文件的类
// FileInputStream这是其中的一个典型实现
// 构造方法中指定要打开的文件名,可以是相对路径,也可以是绝对路径,也可以是File对象
// 这样写就可以不要手动的关闭文件
try(InputStream inputStream = new FileInputStream("./a.txt")){
while (true){
int b = inputStream.read(); // 每次读出一个字节
if(b == -1){
break;
}
System.out.println(b);
}
}catch (IOException e){
e.printStackTrace();
}
try(InputStream inputStream = new FileInputStream("./a.txt")){
byte[] buffer = new byte[1024];
while (true){
int len = inputStream.read(buffer); //把文件的数据尽可能的读到这个数组里
// 最后一次成功的读取,返回的结果是实际的长度 小于1024
// 当文件读完了再次尝试读,返回结果就是-1
if(len == -1){
break;
}
// 循环输出
for(byte b:buffer){
System.out.println(b);
}
// 转成字符串
String str = new String(buffer,0,len,"UTF-8");
System.out.println(str);
}
}catch (IOException e){
e.printStackTrace();
}
为啥通过int来接收?
返回值是-1,255.0-255本来就表示字节表示的范围。byte表示的范围是-128,127.此处用正整数的方式要比带负数的方式变现要好。
原因:读出来的只是表示一个单纯的字节,并不是真的要参加加减乘除运算
读到文件末尾,就返回-1,表示读完了
按照字节的方式读,读出来的结果就相当于这些字符的ascii码。要是存的是中文,读的是中文的每个字节的编码,GBK/UTF-8
4.2.2 利用Scanner进行字符读取
对字符类型直接使用InputStream进行读取是比较麻烦且困难的,利用Scanner会简单一点。
构造方法 | 说明 |
Scanner(InputStream is,string charSet) | 使用charset字符集进行is的扫描读取 |
try(InputStream inputStream = new FileInputStream("./a.txt")){
try(Scanner scanner = new Scanner(inputStream,"UTF-8")){
while(scanner.hasNext()){
String s = scanner.next();
System.out.println(s);
}
}
}catch(IOException e){
e.printStackTrace();
}
4.2.3 利用Reader/FileReader读取文件
public static void main(String[] args) {
try(Reader reader = new FileReader("./a.txt")){
while (true){
int ret = reader.read();
if(ret == -1){
break;
}
System.out.println((char)ret);
}
}catch (IOException e){
e.printStackTrace();
}
}
4.2.4 利用OutputStreamWriter进行字符写入
方法 | 说明 |
write(int b) | 写入文件的数据 |
write(byte[] b) | 将b这个字符数组中的数据全部写入os中 |
write(byte[] b,int off,int len) | 将b这个字符数组中从off开始的数据写入os中,共写len个 |
close() | 关闭字节流 |
flush() | 重要:我们知道 I/O 的速度是很慢的,所以,大多的 OutputStream 为了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置,调用 flush(刷新)操作,将数据刷到设备中。 |
try(OutputStream outputStream = new FileOutputStream("./a.txt")){
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
byte[] bytes = {'1','a','b','c','d'};
outputStream.write(bytes,1,2);
outputStream.flush();
}catch (IOException e){
e.printStackTrace();
}
五、示例
例子1:扫描指定目录,并找到包含指定字符的所有普通文件,并询问用户是否要删除该文件
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
// 1.先让用户输入一个带扫描的路径
System.out.println("请输入要扫描的目录路径: ");
String deleteDirPath = scanner.next();
// 2.判断路径是否合法
File deleteDir = new File(deleteDirPath);
if(!deleteDir.isDirectory()){
// 如果给定的这个路径不存在, 或者不是目录, 就直接提示出错!
System.out.println("您输入的扫描路径非法!");
return;
}
// 3.输入要删除的文件
System.out.println("请输入要删除的文件名: ");
String deleteFileName = scanner.next();
// 4.遍历当前的目录,直到所有和待删除文件匹配的文件
List<File> result = new ArrayList<>();
scanzDir(deleteDir,deleteFileName,result);
// 5.进行删除操作,把result里面所有的文件,都依次进行删除
for(File f:result){
System.out.println(f.getCanonicalPath()+" 该文件是否确认删除? Y/n");
String choice = scanner.next();
if (choice.equals("Y")) {
f.delete();
System.out.println(f.getCanonicalPath() + " 该文件删除成功!");
}
}
}
public static void scanzDir(File deleteDir,String deleteFileName,List<File> result){
File[] files = deleteDir.listFiles(); //
for(File file:files){
try {
System.out.println("扫描了文件"+file.getCanonicalPath());
// 如果是普通文件,就直接判断名字
if(file.isFile()){
if(file.getName().equals(deleteFileName)){
result.add(file);
}
// 如果是文件夹,就需要递归调用
}else if(file.isDirectory()){
scanzDir(file,deleteFileName,result);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
例子2:普通文件的复制
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要复制的源文件:");
String ori = scanner.next();
System.out.println("请输入要复制的目标文件:");
String desc = scanner.next();
File oriFile = new File(ori);
if(!oriFile.isFile()){
System.out.println("当前输入的路径有误,不合法");
return;
}
// 进行复制操作,把ori的内容一个字节一个字节的读出来,写入到desc中
try(InputStream inputStream = new FileInputStream(ori)){
try(OutputStream outputStream = new FileOutputStream(desc)){
byte[] buffer = new byte[1024];
while (true){
// 把读到的内容放入 buffer 中, 返回实际读到的字节数.
int len = inputStream.read(buffer);
if(len == -1){
break;
}
// 读取数据成功, 写入到 outputStream 中
outputStream.write(buffer,0,len);
}
}
}catch (IOException e){
e.printStackTrace();
}
}
例子3:扫描指定目录,找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
public static void main(String[] args) throws IOException {
//1.先输入目录和要查找的内容
Scanner scanner = new Scanner(System.in);
// 1. 先输入目录和要查找的内容
System.out.println("请输入要遍历的目录: ");
String rootDirPath = scanner.next();
System.out.println("请输入要查找的内容: ");
String content = scanner.next();
File rootDir = new File(rootDirPath);
if (!rootDir.isDirectory()) {
System.out.println("输入的路径存在问题!");
return;
}
// 2.进行遍历,找到所有复合要求的文件
List<File> results = new ArrayList<>();
ScanDirByContent(rootDir,content,results);
//3.打印一下找到的文件结果
for(File f:results){
System.out.println(f.getCanonicalPath());
}
}
public static void ScanDirByContent(File rootDir,String content,List<File> results){
File[] files = rootDir.listFiles();
// 空目录
if(files == null){
return;
}
for(File f:files){
if(f.isFile()){
if(isContentExists(f,content)){
results.add(f);
}
}else if(f.isDirectory()){
ScanDirByContent(f,content,results);
}
}
}
public static boolean isContentExists(File f,String content){
// 读取f中的内容,放到一个String中
StringBuilder stringBuilder = new StringBuilder();
try(InputStream inputStream = new FileInputStream(f)){
while (true){
int ret = inputStream.read();
if(ret == -1){
break;
}
stringBuilder.append((char)ret);
}
}catch (IOException e){
e.printStackTrace();
}
// 判定 content 是否是读取的 StringBuilder 的子串
// indexOf 如果找到了子串, 就返回合理下标. 没找到子串, 就返回 -1
return stringBuilder.indexOf(content) != -1;
}