我们先来认识狭义上的文件(file)。针对硬盘这种持久化存储的I/O设备,当我们想要进行数据保存时,往往不是保存成一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概念,就类似办公桌上的一份份真实的文件一般
文件除了有数据内容之外,还有一部分信息,例如文件名、文件类型、文件大小等并不作为文件的数据而存在,我们把这部分信息可以视为文件的元信息
同时,随着文件越来越多,对文件的系统管理也被提上了日程,如何进行文件的组织呢,一种合乎自然的想法出现了,就是按照层级结构进行组织 —— 也就是我们数据结构中学习过的树形结构。这样,一种专门用来存放管理信息的特殊文件诞生了,也就是我们平时所谓文件夹(folder)或者目录(directory)的概念。
如何在文件系统中如何定位我们的一个唯一的文件就成为当前要解决的问题,但这难不倒计算机科学家,因为从树型结构的角度来看,树中的每个结点都可以被一条从根开始,一直到达的结点的路径所描述,而这种描述方式就被称为文件的绝对路径(absolute path)。
C:\Program Files (x86)\Microsoft\Temp\EU5153.tmp
除了可以从根开始进行路径的描述,我们可以从任意结点出发,进行路径的描述,而这种描述方式就被称为相对路径(relative path),相对于当前所在结点的一条路径
.\表示当前目录
. .\表示上级目录
这里的分隔符可以是,也可以是/,但是为了避免\与字符组合i形成转义字符,需要用\来转义一下其中的一个,
即使是普通文件,根据其保存数据的不同,也经常被分为不同的类型,我们一般简单的划分为文本文件和二进制文件,分别指代保存被字符集编码的文本和按照标准格式保存的非被字符集编码过的文件。Windows 操作系统上,会按照文件名中的后缀来确定文件类型以及该类型文件的默认打开程序。但这个习俗并不是通用的,在 OSX、Unix、Linux 等操作系统上,就没有这样的习惯,一般不对文件类型做如
此精确地分类。
文件由于被操作系统进行了管理,所以根据不同的用户,会赋予用户不同的对待该文件的权限,一般地可以认为有可读、可写、可执行权限
Windows 操作系统上,还有一类文件比较特殊,就是平时我们看到的快捷方式(shortcut),这种文件只是对真实文件的一种引用而已。其他操作系统上也有类似的概念,例如,软链接(soft link)等。
最后,很多操作系统为了实现接口的统一性,将所有的 I/O 设备都抽象成了文件的概念,使用这一理念最为知名的就是 Unix、Linux 操作系统 —— 万物皆文件
技巧:
将文件拖入记事本,能看懂就是文本文件,看不懂就是二进制文件
站在CPU的角度,将数据从内存读到硬盘中,是输出,从硬盘读取到内存中,是输入
本节内容中,我们主要涉及文件的元信息、路径的操作,暂时不涉及关于文件中内容的读写操作。
Java 中通过 java.io.File 类来对一个文件(包括目录)进行抽象的描述。注意,有 File 对象,并不代表真实存在该文件
package Thread;
import java.io.File;
import java.io.IOException;
public class demo1 {
public static void main(String[] args) throws IOException {
File file = new File("d:/text.txt");
System.out.println(file.getParent());
System.out.println(file.getName());
System.out.println(file.getPath());
System.out.println(file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
}
}
package Thread;
import java.io.File;
import java.io.IOException;
public class demo1 {
public static void main(String[] args) throws IOException {
File file = new File("./text.txt");
System.out.println(file.getParent());
System.out.println(file.getName());
System.out.println(file.getPath());
System.out.println(file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
}
}
这里的getAbsolutePath会将工作路径与设定的当前路径相结合,工作路径就是程序所在的路径,getCanonicalPath则是会将getAbsolutePath合成的目录进行整理优化,去掉冗余的部分
package Thread;
import java.io.File;
public class demo2 {
public static void main(String[] args) {
File file = new File("./text.txt");
System.out.println(file.exists());
System.out.println(file.isFile());
System.out.println(file.isAbsolute());
}
}
package Thread;
import java.io.File;
import java.io.IOException;
public class demo2 {
public static void main(String[] args) throws IOException {
File file = new File("./text.txt");
file.createNewFile();
System.out.println(file.exists());
System.out.println(file.isFile());
System.out.println(file.isAbsolute());
}
}
可以看出,第一段代码,由于并没有创建一个text.txt的文件,所以打印都是false,而第二个代码,从目录中可以看到,已经将text.txt创建出来了,由于是newfile创建出来的,所以打印结果是true true false
package Thread;
import java.io.File;
public class demo3 {
public static void main(String[] args) throws InterruptedException {
File file = new File("./text.txt");
// file.delete();
file.deleteOnExit();
Thread.sleep(5000);
}
}
上述代码,delate和deleteOnExit都可以将文件删除,不同的是,deleteOnExit是需要等到程序结束后才会将文件删除
package Thread;
import java.io.File;
import java.util.logging.FileHandler;
public class demo4 {
public static void main(String[] args) {
File file = new File("./textDir");
//mkdir一次只能创建一个目录,mkdirs一次可以创建很多个目录
file.mkdir();
// file.mkdirs();
}
}
package Thread;
import java.io.File;
import java.util.logging.FileHandler;
public class demo4 {
public static void main(String[] args) {
File file = new File("./textDir/111/222/333");
//mkdir一次只能创建一个目录,mkdirs一次可以创建很多个目录
// file.mkdir();
file.mkdirs();
}
}
package Thread;
import java.io.File;
public class demo5 {
public static void main(String[] args) {
File file = new File("./test.txt");
File file2 = new File("./test2.txt");
file.renameTo(file2);
}
}
我们先将file文件给创建出来,再利用rename将file文件名改为filename
rename还可以起到文件移动的作用
package Thread;
import java.io.File;
public class demo5 {
public static void main(String[] args) {
File file = new File("./test.txt");
File file2 = new File("./src/test2.txt");
file.renameTo(file2);
}
}
此时文件test.txt已经从src目录外面给挪到src里面去了
文件这里的内容本质是来自于硬盘,硬盘又是操作系统管理的,使用某个编程语言操作文件,本质上都是需要调用系统的api,
虽然不同的编程语言,操作文件的api有所差别,但是,基本步骤都是一样的,
文件内容的操作核心步骤,有四个
1.打开文件
2.读文件
3.写文件
4.关闭文件
javaIO流是一个比较庞大的体系,涉及到非常多的类,这些不同类,都有各自不同的特性,但是总的来说,使用方法都是类似的,
1.构造方法,打开文件
2.close方法,关闭文件,
3.如果衍生自InputStream或者Read,就可以使用read方法来读数据
4.如果衍生自OutputStreadm或者Writer,就可以使用writer方法来读数据
接下来,我们来举个例
后续的一些操作字符的类都是衍生自这两个类,是操作字符为单位(文本文件)
先提前再d盘创建一个文件,写入一些数据后,我们尝试将利用程序其打开并读取
package Thread;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
//Read的使用
public class demo6 {
public static void main(String[] args) throws IOException {
Reader reader = new FileReader("D:/Data/test.txt");
try {
}finally {
reader.close();
}
}
}
这里的close非常重要,是用来释放不必要的资源的,这是因为,让一个进程打开一个文件,是需要从系统中申请一定的资源的(占用进程的pcb里的文件描述符表中的资源,这里的表是一个顺序表,长度有限,不会自动扩容)
,如果不释放,就会出现"文件资源泄露"这个很严重的问题,一旦一直打开文件,而又不去关闭不用的文件,文件描述符表就会被占满,后续就无法继续打开新的文件了
上述的finally虽然也能够解决问题,但是不够优雅,我们可以使用try with resources
package Thread;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
//Read的使用
public class demo6 {
public static void main(String[] args) throws IOException {
try(Reader reader = new FileReader("D:/Data/test.txt")){
//只要try代码块执行完了,就会自动调用到close
//括号里面还可以创建多个对象,用;隔开
}
}
}
文件流中的任意对象,都可以按照上述的讨论来进行close
package File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class demo1 {
public static void main(String[] args) {
try(Reader reader = new FileReader("d:/data/test.txt")) {
// reader.read();//从文件中读取一个字节,返回类型是一个两个字节的Integer
while (true){
char []buf = new char[1024];
int n = reader.read(buf);//通过read,就会把本来是一个空的数组,填充上内容,此处的参数就是一个输出型参数
//这里的返回值是一个整型变量,表示实际读取到的字符个数,如果文件访问到末尾了,比如eof,就会返回一个-1
if (n==-1){
break;
}
for (int i =0;i<n;i++){
System.out.print(buf[i]+" ");
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
还有这个方法,主要用在从多个文件中读取不同位置的数据.
写文件: 输出,使用方法和输入非常相似,关键操作是write,write之前要打开文件,用完了也需要关闭文件
输出流对象(无论是字节流还是字符流),都会在打开文件之后,清空文件内容
Write这个方法可以直接传输一个字符串,这是非常方便的
package File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class demo4 {
public static void main(String[] args) throws IOException {
try(Writer writer = new FileWriter("d:/data/test.txt")){
writer.write("hello java");
}
}
}
若想把新的内容加在之前的内容后面,就可以采用追加的方式,也就是在打开文件的时候,添加一个参数
package File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class demo4 {
public static void main(String[] args) throws IOException {
try(Writer writer = new FileWriter("d:/data/test.txt",true)){
writer.write("hello java2");
}
}
}
后续的一些操作字节的类都是衍生自这两个类,是操作字节为单位(二进制文件)
InputStream是字节流,用法和Reader非常相似,文本文件也可以用字节流打开,只不过此时读到的每个字节,就不是完整的字符了
package File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class demo2 {
public static void main(String[] args) throws IOException {
try(InputStream inputStream = new FileInputStream("d:/data/test.txt")){
// inputStream.read();//一次读一个字节,虽然返回值是int,但是实际上只有0-255,也就是一个字节的大小
while (true){
byte []buf = new byte[1024];
int n = inputStream.read(buf);//一次读若干字节,尝试填满数组
if (n==-1){
break;
}
for (int i =0;i<n;i++){
System.out.printf(" %x ", buf[i]);
}
}
}
}
}
public class demo3 {
public static void main(String[] args) throws IOException {
try(InputStream inputStream = new FileInputStream("d:/data/test.txt")){
Scanner scanner = new Scanner(inputStream);
String s = scanner.next();
System.out.println(s);
}
}
}
以前学习的Scanner操作,在这里完全适用,但是,要注意,Scanner只是用来读取文本文件的,不适合读取二进制文件
OutputStream使用方式和Write完全一样,只是不能使用字符串参数,只能按照字节,或者字节数来写入
Scanner搭配inputStream 达到简化代码的效果,
PrintWriter搭配OutputStream,提供了一系列的方法,printf,println
删除该文件
package File;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
//扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
// 注意:我们现在的方案性能较差,所以尽量不要在太复杂的目录下或者大文件下实验
public class demo5 {
public static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) throws IOException {
System.out.println("请输入想要查找的根目录");
File dir = new File(scanner.next());
System.out.println("请输入想要查找的文件");
File file = new File(scanner.next());
if (!dir.isDirectory())
return;
ScanDir(dir,file);
}
public static void ScanDir(File dir , File file) throws IOException {
File [] files = dir.listFiles();
if (files.length==0||files==null)
return;
for (File file1:files) {
System.out.println(file1.getCanonicalPath());
if (file.isDirectory()){
ScanDir(file1,file);
} else {
DealFile(file1,file);
}
}
}
public static void DealFile(File dir , File file){
if (dir!=file)
return;
System.out.println("是否要删除该文件(Y/N)");
String s = scanner.next();
if (s=="Y"||s=="N")
file.delete();
}
}
package File;
import java.io.*;
import java.util.Scanner;
//进行普通文件的复制
public class DEMO6 {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入想要复制的文件");
String file1 = scanner.next();
File srcFile = new File(file1);
if (!srcFile.isFile())
{
return;
}
System.out.println("请输入文件复制的目的地");
String file2 = scanner.next();
File destFile = new File(file2);
if (!destFile.getParentFile().isDirectory())
return;
try(InputStream inputStream = new FileInputStream(srcFile);
OutputStream outputStream = new FileOutputStream(destFile)){
byte[]buf = new byte[1024];
while (true){
int n = inputStream.read(buf);
if (n==-1)
break;
outputStream.write(buf,0,n);
}
}
}
}