普通文件是保存在硬盘上面的。
在程序有的角度,文件主要分为两种:
判断一个文件是不是二进制编码,用记事本打开就好。打开是乱码,就是二进制文件,不是乱码就是文本文件。就像打开一个图片:
用记事本打开的话,就是这个样子:
是乱码,就说明这个图片在存储的时候是以二进制存储的。
常见的文件类型
在操作系统中,通过“路径”这样的概念来描述一个具体文件/目录的位置。
.
或 ..
开头的,其中 .
表示当前路径 ..
表示当前路径的父目录(上级路径)。例如:以 D:\D:\BaiduNetdiskDownload\壁纸
为基准目录,找到 2月日历.png 就是这样:./2月日历.png
。图片位置如下:
因为在同一个目录里,所以就是 ./2月日历.png
。
如果要找到上一级的其它文件,比如 教父1 ,就是这样 ../教父1.MP4
文件位置如下:
这里的 ..
就表示先回到上一级路劲,然后再从上一级路径中寻找 教父1.MP4 这个文件。
Java 当中的文件操作,主要有两类:
在使用绝对路径的时候,在 File 的构造方法中写出来就行了,建议用 反斜杠,如果是用斜杠的话,就得再用一个斜杠来转义。所以建议用 反斜杠。代码如下:
public static void main(String[] args) throws IOException {
//通过绝对路径来定位。
File f1 = new File("d/Test1.txt");
//获取到文件的父目录
System.out.println(f1.getParent());
//获取到文件名
System.out.println(f1.getName());
//获取到文件路径
System.out.println(f1.getPath());
//获取到绝对路径
System.out.println(f1.getAbsolutePath());
//获取到绝对路径
System.out.println(f1.getCanonicalPath());
}
测试代码如下:
public static void main(String[] args) throws IOException {
File f2 = new File("./Test1.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());
}
运行结果如下:
基准路径就是这里:
然后后面把相对路径拼接上去,.
可以省略,所以就有了下面的绝对路径。
通过绝对路径来看,文件是否存在,是否是一个目录,是否是一个普通文件:
public static void main(String[] args) {
File f = new File("d:/Test1.txt");
//判断文件是否存在
System.out.println(f.exists());
//判断文件是否是一个目录
System.out.println(f.isDirectory());
//判断文件是否是一个普通文件
System.out.println(f.isFile());
}
通过相对路径来看,文件是否存在,是否是一个目录,是否是一个普通文件:
public static void main(String[] args) {
//换成相对路径就全是 false 了
File f = new File("./Test1.txt");
//判断文件是否存在
System.out.println(f.exists());
//判断文件是否是一个目录
System.out.println(f.isDirectory());
//判断文件是否是一个普通文件
System.out.println(f.isFile());
}
运行结果如下:
因为当前项目中,并没有创建这样的文件,所以都是 false。
通过 createNewFile 来创建文件,代码如下:
public static void main(String[] args) throws IOException {
//文件的创建和删除
File f = new File("./Test1.txt");
System.out.println(f.exists());
System.out.println("创建文件");
f.createNewFile();
System.out.println("创建文件结束");
System.out.println(f.exists());
}
通过 delete 方法直接删除,代码如下:
public static void main2(String[] args) {
File f = new File("./Test1.txt");
//删除文件,直接删除
f.delete();
}
通过 mkdir 来创建目录。代码如下:
public static void main(String[] args) {
File f = new File("./aaa");
//创建目录
f.mkdir();
//说明已经创建好目录了。
System.out.println(f.isDirectory());
}
通过 mkdirs 来创建多级目录。代码如下:
public static void main(String[] args) {
//创建多级目录
File f = new File("./aaa/bbb/ccc/ddd");
f.mkdirs();
System.out.println(f.isDirectory());
}
运行结果如下:
然后从项目目录当中就可以看到创建的多级目录了:
代码如下:
public static void main1(String[] args) {
File f = new File("./");
//把 ./ 目录下所有的目录全部列出来
System.out.println(Arrays.toString(f.list()));
}
代码如下:
public static void main(String[] args) {
File f = new File("./");
//通过 File 对象来输出。
System.out.println(Arrays.toString(f.listFiles()));
}
通过 renameTo 来重命名文件。代码如下:
public static void main(String[] args) {
File f = new File("./aaa");
File f2 = new File("./zzz");
//把 aaa 的名字改成 zzz
f.renameTo(f2);
}
针对文件内容的读写,Java 标准库提供了一组类。按照文件的内容,分成了两个系列:
上面这些抽象类,既可以针对普通文件进行读写,也可以针对特殊文件(网卡,socket)进行读写。
这些类都是抽象类,实际使用的都是这些类的子类:
上面这一组都是针对普通文件进行读写的。
流只是一个形象的比喻。通过流对象来读取 100 个字节,可以一次读十个字节,十次读完。也可以一次读 20 字节,五次读完。
使用的时候,需要在构造方法中指定打开文件的路径。可以是绝对路径,也可以是相对路径。通过 try catch 来处理 FileNotFoundException 异常。代码如下:
public static void main(String[] args) {
//方法中需要指定打开文件的路径。
InputStream inputStream = null;
try {
//1、创建对象,同时也是在打开文件
inputStream = new FileInputStream("d:/Test1.txt");
//2、尝试一个一个字节的读,把文件都读完
//读文件的时候,也可很容易读取失败。硬盘很容易出现坏道,
while (true) {
int b = inputStream.read();
if (b == -1) {
//读到了文件末尾
break;
}
System.out.println(b);
}
//用完关闭文件,写在 finally 里面会更好,因为如果有异常的话,也可以继续关闭资源。
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果如下:
因为我们文件当中的内容是:abcdef。所以读出来就是:97 98 99 100 101 102
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("d:/Test1.txt")){
//一次读若干个字节
while (true) {
byte[] buffer = new byte[1024];
//这个操作是把读出来的结果放到了 buffer 数组里了。相当于是使用 参数 来表示方法的返回值
// 这种做法称为”输出型参数“,这种操作在 Java 中少见,C++ 当中常见。
// 相当于把餐盘给阿姨,阿姨打好饭再给你
int len = inputStream.read(buffer);
if (len == -1) {
//读到了文件末尾,读取完毕
break;
}
for (int i = 0; i < len; i++) {
System.out.println(buffer[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
使用字节流 OutputStream 来写文件。代码如下:
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("d:/Test1.txt")) {
//一次写入一个字节
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
} catch (IOException e) {
e.printStackTrace();
}
}
这里写的内容就是 a,b,c 因为是根据编码来写入的。文件中如下:
代码如下:
public static void main(String[] args) {
try (Reader reader = new FileReader("d:/Test1.txt")) {
//按照字符来读
while (true) {
char[] buffer = new char[1024];
int len = reader.read(buffer);
if (len == -1) {
break;
}
String s = new String(buffer, 0, len);
System.out.println(s);
}
} catch (IOException e) {
e.printStackTrace();
}
}
通过 Writer 来实现:
public static void main(String[] args) {
try (Writer writer = new FileWriter("d:/Test1.txt")) {
writer.write("syz");
} catch (IOException e) {
e.printStackTrace();
}
}
用户输入一个目录,再输入一个要删除的文件名。找到名称中包含指定字符的所有普通文件(不包含目录),并且询问用户是否要删除该文件。不过要注意的是:
代码如下:
public class Test {
public static void main(String[] args) {
//先输入要扫描的目录,以及要删除的文件名。
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要扫描的路径");
String rootDirPath = scanner.next();
System.out.println("请输入要删除的文件名");
String teDeleteName = scanner.next();
File rootDir = new File(rootDirPath);
if (!rootDir.isDirectory()) {
System.out.println("输入的扫描路径有误");
return;
}
//输出所有的目录。输出的时候,遍d历方式也是递归
scanDir(rootDir,teDeleteName);
}
private static void scanDir(File rootDir, String teDeleteName) {
//1、先列出 rootDir 中有哪些内容。
File[] files = rootDir.listFiles();
if (files == null) {
//rootDir 是一个空目录
return;
}
//遍历当前列出的这些内容,如果是普通文件,就检测文件名是否是要删除的文件。
// 如果是目录,就递归进行遍历
for (File f : files) {
if (f.isFile()) {
if (f.getName().contains(teDeleteName)) {
deleteFile(f);
}
} else if (f.isDirectory()) {
scanDir(f,teDeleteName);
}
}
}
private static void deleteFile(File f) {
try {
System.out.println(f.getCanonicalPath() + "确定要删除吗(Y/N)");
Scanner scanner = new Scanner(System.in);
String choice = scanner.next();
if (choice.equals("Y") || choice.equals("y")) {
f.delete();
System.out.println("文件删除成功");
} else {
System.out.println("文件取消删除");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果如下:
然后打开文件夹查看,发现 Test1 以经被删除了:
让用户指定两个文件:一个是原路径(被复制的文件),一个是目标路径(复制后的文件路径)。要注意的是:在复制的时候不需要检查目标文件是否存在,OutputStream 写文件的时候能够自动创建不存在的文件。代码如下:
public static void main(String[] args) {
//1、输入两个路径
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要拷贝的源路径");
String src = scanner.next();
System.out.println("请输入要拷贝的目标路径");
String dest = scanner.next();
File srcFile = new File(src);
if (!srcFile.isFile()) {
System.out.println("输入的源路径不正确!");
return;
}
//2、读取源文件,拷贝到目标文件当中
try (InputStream inputStream = new FileInputStream(src)) {
try (OutputStream outputStream = new FileOutputStream(dest)) {
//把 inputStream 中的数据读出来,写入到 outputStream 中
byte[] buffer = new byte[1024];
while (true) {
int len = inputStream.read(buffer);
if (len == -1) {
//读取完毕
break;
}
//写入的时候,不能把整个 buffer 都写进去,毕竟 buffer 可能是只有一部分才是有效数据。
outputStream.write(buffer, 0, len);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
运行结果如下:
我们打开目标文件夹,就可以看到拷贝好的文件了:
普通文件可以拷贝,二进制文件也可以拷贝。
代码如下:
public static void main(String[] args) {
//输入要扫描的文件路径
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要扫描的路径");
String rootDirPath = scanner.next();
System.out.println("请输入要查询的关键词");
String word = scanner.next();
File rootDir = new File(rootDirPath);
if (!rootDir.isDirectory()) {
System.out.println("输入的路径违法");
return;
}
//递归并遍历,针对普通文件和目录分别处理
scanDir(rootDir, word);
}
private static void scanDir(File rootDir, String word) {
//1、先列出 rootDir 中有哪些内容。
File[] files = rootDir.listFiles();
if (files == null) {
//rootDir 是一个空目录
return;
}
for (File f : files) {
if (f.isFile()) {
//针对文件内容进行查找
if (containsWord(f, word)) {
try {
System.out.println(f.getCanonicalPath());
} catch (IOException e) {
e.printStackTrace();
}
}
} else if (f.isDirectory()) {
scanDir(f,word);
}
}
}
private static boolean containsWord(File f, String word) {
StringBuilder stringBuilder = new StringBuilder();
//把 f 中的内容都读出来,放到 StringBuilder 中
try (Reader reader = new FileReader(f)) {
char[] buffer = new char[1024];
while (true) {
int len = reader.read(buffer);
if (len == -1) {
break;
}
stringBuilder.append(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
//indexOf 返回的是子串的下标,如果 word 在 StringBuilder 中不存在,就返回 -1
return stringBuilder.indexOf(word) != -1;
}
就是刷新缓冲区。就像吃瓜子,一次抓很多瓜子。一次抓很多瓜子的手就是 “输入缓冲区”。如果吃完瓜子,把瓜子皮放在一个纸巾上面,这个纸巾就是输出缓冲区。
缓冲区存在的意义: 缓冲区存在的意义就是为了提高效率,在 计算机 当中很重要,就像 CPU 读取内存的速度,比读取硬盘的速度高很多。所以缓冲区就是一次性读一堆数据放在缓冲区,这样就增加了之后读取的效率。
写数据的时候,需要先把数据写在缓冲区,然后再统一写硬盘。如果缓冲区已经满了,就触发写硬盘操作。如果缓冲区还没满,也想写在硬盘里,就可以通过 flush 来手动刷新缓冲区。