目录

文件

针对文件系统的操作

File

针对文件内容的操作

字节流

InputStream

FileInputStream

OutputStream

FileOutputStream

字符流

Reader

FilerReader

Writer

FileWriter

练习

练习1

练习2

文件

文件:在这里我们讨论的文件指的是硬盘上的文件。文件除了有保存的数据内容之外,还有部分信息,如文件名、文件类型等不作为文件的数据而存在,这部分信息可视为文件的元信息。

文件的分类:一般将文件划分为文本文件(保存字符集编码的文本)和二进制文件(按照标准格式保存的非被字符集编码过的文件)

文本文件:按照字符串的方式来理解文件内容(文本文件里的二进制内容,都是表示的字符串),即文本文件中的内容,都是合法的字符(即字符编码中的字符)

二进制文件:二进制文件没有文本文件上述限制,可以存储任何数据

如何判断一个文件是文本文件还是二进制文件?

 我们可以借助记事本来判断,使用记事本打开一个文件,若看到的是正常的内容,则这个文件是文本文件;若是乱码,则是二进制文件

树形结构组织文件:文件系统是按照层级结构来组织文件的(也就是数据结构中学过的树形结构),这里的树是N叉树,每一个目录文件中可以有很多个子节点,而每一个普通文件(非目录文件)是一个叶子节点。

路径:操作系统中使用“路径”来描述一个具体文件的位置。例如:C:\Users\data\test.txt,表示从根节点出发(Windows系统是从盘符出发),一级一级往下走,直到找到目标文件,将中间经过的所有目录的名字串联起来,使用 / 或 \ 分隔

路径分隔符:分隔符的作用类似于标点符号,用来分隔路径列表中的文件名。上述C:\Users\data\test.txt中的\就是路径分隔符

在Linux中使用的路径分隔符是斜杠 /,而在windows中既可以使用 反斜杠 \ 也可以使用 /。由于在Java中,\ 是一个特殊的字符,被作为转义字符使用,因此,若我们使用 \ 分隔路径时,要使用 反斜杠符(\\)来表示字面意义上的 \ ,即(C:\\Users\\data\\test.txt)

路径的表示方式:

路径有两种风格的表示方式:

绝对路径:从树根节点出发(Windows则是从盘符开始)一层一层到目标文件(如C:\Users\data\test.txt)

相对路径:以当前所在目录为依据,找到目标文件。使用 . 表示当前所在目录,(当前在data目录,则 .\test.txt),..表示上层目录,(若目标文件为:C:\Users\t\data.txt,则相对路径为../t/data.txt)

Java中的文件操作主要分为两类:

1. 针对文件系统的操作,如创建文件、删除文件等

2. 针对文件内容的操作,如读文件、写文件等

Java中针对文件系统的操作,使用 File 类来对一个文件(包括目录)进行抽象的描述(有File对象,并不代表该文件真实存在),这个类在 java.io 包中

io:即 input(输入)和 output(输出)

以CPU为参照,数据从 硬盘 到 CPU 叫做 输入,从 CPU 到 硬盘 叫做 输出

针对文件系统的操作

针对文件系统的操作通常是通过 File 类来实现的

File

我们通过学习File类中的常见属性、构造方法和方法来了解 File 类

属性:

修饰符及类型属性说明static StringpathSeparator依赖于系统的路径分隔符,String类型的表示static StringpathSeparator依赖于系统的路径分隔符,char类型的表示

使用的路径分隔符根据系统自动调整

构造方法:

构造方法说明File(File parent, String child)通过父目录 + 孩子文件路径,创建一个File实例File(String pathname)通过文件路径创建一个File实例,路径可以是绝对路径也可以是相对路径File(String parent, String child)通过父目录 + 孩子文件路径,创建一个File实例,父目录用路径表示

方法:

返回值类型方法说明StringgetParent()返回 File 对象的父目录文件路径StringgetName()返回 File 对象的纯文件名称StringgetPath()返回 File 对象的文件路径StringgetAbsolutePath()返回 File 对象的绝对路径StringgetCanonicaPath()返回 File 对象的修饰过的绝对路径booleanexists()判断 File 对象描述的文件是否真实存在booleanisDirectory()判断 File 对象代表的文件是否是一个目录booleanisFile()判断 File 对象代表的文件是否是一个普通文件booleancreateNewFile()根据 File 对象,自动创建一个空文件。创建成功后返回truebooleandelete()根据 File 对象,删除该文件。删除成功后返回truevoiddeleteOnExit()根据 File 对象,标记该文件将被删除,删除操作将会在JVM运行结束时进行String[]list()返回 File 对象代表的目录下的所有文件名File[]listFiles()返回 File 对象代表的目录下的所有文件,以File 对象表示booleanmkdir()创建 File 对象代表的目录booleanmkdirs()创建 File 对象代表的目录,如果是多级目录,则会创建中间目录booleanrenameTo(File dest)进行文件改名booleancanRead()判断用户是否对文件有可读权限booleancanWrite()判断用户是否对文件有可写权限

我们先观察不同get方法的特点及它们之间的差异:

以相对路径的方式创建File对象:

package IO;

import java.io.File;

import java.io.IOException;

public class Demo1 {

public static void main(String[] args) throws IOException {

File file = new File("./test");//并不要求该文件真实存在

System.out.println(file.getParent());//父目录文件路径

System.out.println(file.getName());//纯文件名

System.out.println(file.getPath());//文件路径

System.out.println(file.getAbsoluteFile());//绝对路径

System.out.println(file.getCanonicalFile());//File对象修饰过的绝对路径

}

}

运行结果:

以绝对路径的方式创建File对象: 

package IO;

import java.io.File;

import java.io.IOException;

public class Demo1 {

public static void main(String[] args) throws IOException {

File file = new File("C:\\Users\\whisper\\Desktop\\code\\Java\\java\\test_1_21\\test");//并不要求该文件真实存在

System.out.println(file.getParent());//父目录文件路径

System.out.println(file.getName());//纯文件名

System.out.println(file.getPath());//文件路径

System.out.println(file.getAbsoluteFile());//绝对路径

System.out.println(file.getCanonicalFile());//File对象修饰过的绝对路径

}

}

运行结果: 

通过观察上述运行结果我们发现:当使用绝对路径创建 File对象时,其文件路径、绝对路径和File修饰过的绝对路径相同;而使用相对路径创建 File 对象时,则不同,文件路径表示创建的相对路径,绝对路径与 File 对象修饰过的绝对路径相比,包含.\

普通文件的创建:

package IO;

import java.io.File;

import java.io.IOException;

public class Demo2 {

public static void main(String[] args) throws IOException {

File file = new File("./test.txt");

System.out.println(file.exists());//判断文件是否存在

System.out.println(file.isFile());//判断文件是否是普通文件

System.out.println(file.isDirectory());//判断文件是否是目录

boolean ret = file.createNewFile();//根据 File 对象,自动创建一个空文件

System.out.println(ret);

}

}

运行结果:

文件不存在,因此其既不是普通文件,也不是目录。正是由于文件不存在,因此可以根据 File 对象自动创建一个空文件(当文件存在时,则不能创建,不存在两个相同路径的文件)。

普通文件的删除:

package IO;

import java.io.File;

public class Demo3 {

public static void main(String[] args) {

File file = new File("./test.txt");

boolean ret = file.delete();//删除文件

System.out.println(ret);

}

}

运行结果为:true

当我们在运行Demo2时创建了一个test.txt文件,因此该文件存在,能够删除,也可以使用deleteOnExit()方法,在JVM运行结束时删除该文件

目录的创建:

package IO;

import java.io.File;

public class Demo4 {

public static void main(String[] args) {

File file = new File("./a/b/c");

boolean ret1 = file.mkdir();

boolean ret2 = file.mkdirs();

System.out.println(ret1);

System.out.println(ret2);

}

}

运行结果:

使用mkdik()方法时,若中间目录不存在,则无法成功创建目录,而mkdirs()则可创建多级目录

目录下的所有文件:

package IO;

import java.io.File;

import java.util.Arrays;

public class Demo5 {

public static void main(String[] args) {

File file = new File(".");

File[] files = file.listFiles();//以 File 对象表示

System.out.println(Arrays.toString(files));

String[] strings = file.list();

System.out.println(Arrays.toString(strings));

}

}

运行结果:

 文件重命名:

package IO;

import java.io.File;

public class Demo6 {

public static void main(String[] args) {

File file = new File("test.txt");//要重命名的文件必须存在

File dest = new File("dest.txt");//更改后的文件名不能存在

System.out.println("file:" + file.exists());

System.out.println("dest:" + dest.exists());

System.out.println(file.renameTo(dest));

System.out.println("file: " + file.exists());

System.out.println("dest:" + dest.exists());

}

}

运行结果:

针对文件内容的操作

在学习针对文件内容的操作之前,我们首先来来了解一下数据流的概念

什么是数据流?

数据流是一串连续的数据集合,就像水池里的水流,在一端水管供水(即写数据),在另一端水管出水(即读数据)。在写入数据时,可以一点一点或一段一段地写入,这些数据会按照先后顺序形成一个长的数据流,则在读取数据时,可以读取任意长度的数据

Java标准库对流进行了一系列的封装,提供了一系列类来完成这些工作,而这些类大体可以分为两大类:

1. 字节流:以字节为单位进行读写,一次最少读写一个字节(输入:InputStream 输出:OutputStream)

2. 字符流:以字符为单位进行读写,一次最少读写一个字符,例如,若使用UTF- 8,则一个汉字占3个字节,每次读写都得以一个汉字为单位来进行读写,不能一次读写半个字符(输入:Reader 输出:Writer)

字节流

我们首先来看读文件操作

InputStream

InputStream(输入流),程序从输入流读取数据(数据来源:键盘、文件、网络...)

常用方法:

返回值类型方法说明intread()读取一个字节的数据,当返回-1时则表示已经读取完毕intread(byte[] b)最多读取长度为b.length字节的数据到b数组中,返回实际读取到的数量,当返回-1时表示已经读取完毕intread(byte[] b, int off, int len)最多读取 len 字节的数据到b数组中,从off下标开始存放数据,返回实际读取到的数量,当返回-1时代表已经读取完毕voidclose()关闭字节流

InputStream是一个抽象类,要使用需要具体的实现类。InputStream的实现类有很多(基本可以认为不同的输入设备都可以对应一个InputStream类),在这里我们需要从文件中读取,因此使用的实现类为 FileInputStream

FileInputStream

构造方法:

构造方法说明FileInputStream(File file)利用File构造文件输入流FileInputStream(String name)利用文件路径来构造文件输入流

在读取文件内容时,步骤为:打开文件 读文件 关闭文件

由于我们还没有学习写文件操作,因此我们手动向文件中写入数据,再从文件中读取数据

我们可以通过刚才学习的createNewFile()方法在当前路径下创建一个新文件,再写入数据

//在当前路径下创建文件

File file = new File("./test.txt");

file.createNewFile();

//手动向文件中写入数据:abcdefg

我们首先一个字节一个字节地读取数据:

package IO;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.InputStream;

public class Demo7 {

public static void main(String[] args) throws IOException {

//在当前路径下创建文件

File file = new File("./test.txt");

file.createNewFile();

//手动向文件中写入数据:abcdefg

//打开文件

InputStream inputStream = new FileInputStream(file);

//读文件

while (true){//循环读取,直到读取完数据

int n = inputStream.read();

if(n == -1){//当返回值为-1时,

break;

}

System.out.println(n);//打印读取到的数据

}

//关闭文件

inputStream.close();

}

}

运行结果:

由于我们是直接打印int类型的n,因此打印结果是字符的ASCII码值

read()的返回值类型是int,但其实际取值为:-1 - 255,当读取到文件末尾,继续读取就会返回-1,其他情况下取值为0 - 255

然而,在上述代码中还有一个隐藏的问题:由于上述代码是 按照打开文件、读文件、关闭文件的顺序执行的,因此未出现问题。但若在读取文件的过程中抛出异常或是根据逻辑执行 return 语句,此时close()就未执行到,就会造成文件资源泄露

关闭文件必须执行,因此我们可以使用finally,即:

Java的try操作还提供了一个版本:try with resources 

try ( InputStream inputStream = new FileInputStream(file))//一旦代码出了try代码块,try就会自动调用inputStream的close()方法

注:只有实现了Closeable接口的类,才能放到try()中

也可以使用 read(byte[] b)方法,一次读取多个字节,并将读取到的数据放入b数组中:

package IO;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.InputStream;

public class Demo7 {

public static void main(String[] args) throws IOException {

try ( InputStream inputStream = new FileInputStream("./test.txt")){

//读文件

while (true){//循环读取,直到读取完数据

byte[] buffer = new byte[1024];

int n = inputStream.read(buffer);

if(n == -1){//读取完毕

break;

}

for (int i = 0; i < n; i++) {

System.out.printf("%c", buffer[i]);

}

}

}

}

}

运行结果:

默认情况下,read会将数组填满,但当文件剩余长度不足以填满数组时,返回值就会告诉我们实际填充了多少个字节

而 read(byte[] b, int off, int len) 则是填充数组的一部分,从off下标开始填入数据,填充len长度数据,若文件剩余长度小于len,此时返回值也会告诉我们实际填充了多少个字节

运行结果:

对比以上三种读取数据方式,相比于一次读取一个字节的数据,一次读取多个字节的IO次数更少,性能更好。

以字节为单位进行读写,是否能够读取中文?

我们将test.txt中内容换成中文: 

运行结果:

注:在写入中文时使用的是UTF-8编码,且在String中使用的编码也为UTF-8,因此可以正确读取数据,若我们将编码方式改为“GBK”,则不能正确读取出数据

String s = new String(buffer, 0, n,"GBK");

因此,我们要确保写入数据和读取数据时的编码方式相同,在IDEA中,可在右下角查看使用的编码方式

在我们未学习InputStream之前,我们读取数据是通过Scanner来进行读取的。在上述例子中,对字符类型直接使用InputStream读取比较麻烦,因此,我们可以使用Scanner来进行读取

Scanner(InputStream inputStream, String charset)  //使用charset字符集对inputStream进行扫描读取

package IO;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.InputStream;

import java.util.Scanner;

public class Demo7 {

public static void main(String[] args) throws IOException {

try (InputStream inputStream = new FileInputStream("./test.txt"){

Scanner scanner = new Scanner(inputStream);

//读文件

while (scanner.hasNext()){

String s = scanner.nextLine();

System.out.println(s);

}

}

}

}

在学习了文件读取操作后,我们来看写入数据操作

OutputStream

常用方法:

返回值类型方法说明voidwrite(int b)写入数据bvoidwrite(byte[] b)将数组b中数据都写入intwrite(byte[] b, int off, int len)将数组b中数据从off下标开始写入,一共写len个数据voidclose()关闭字节流voidflush()在进行写入数据时,为了减少设备操作的次数,会将数据先暂时写入内存中一个指定区域里,直到该区域满了或满足其他指定条件时才会将数据写到设备中,这个区域一般称为缓冲区。但我们写入的数据可能会遗留一部分在缓存区中,需要在最后或合适的位置,调用flush()(刷新)操作,将数据刷到设备中

OutputStream同样是一个抽象类,要使用需要具体的实现类。由于我们只关注将数据写入文件,因此使用FileOutputSteam

FileOutputStream

构造方法:

构造方法说明FileOutputStream(File file)利用 File 构造文件输出流FileOutputStream(String name)利用文件路径构造文件输出流

FileOutputStream的使用与FileInputStream的使用类似

package IO;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.OutputStream;

public class Demo8 {

public static void main(String[] args) {

try(OutputStream outputStream = new FileOutputStream("./test.txt")){

//一次写入一个字节数据

outputStream.write(97);

outputStream.write(98);

outputStream.write(99);

//将数组中所有数据都写入

byte[] bytes = {100, 101, 102, 103, 104};

outputStream.write(bytes);

//写入数组中部分数据

outputStream.write(bytes,1, 2);

//将缓存区数据写到文件中

outputStream.flush();

}catch (IOException e){

e.printStackTrace();

}

}

}

运行结果:在test.txt中,我们发现数据已经被写入

但上一次手动输入的“你好”被覆盖成新的数据,多运行几次代码,我们会发现:每次写入的数据都会覆盖掉旧的数据。OutputStream默认情况下,会将文件之前内容清空,然后重新写入数据(清空是打开操作引起的,而不是write)。若我们想接着上次的内容继续写,可以使用续写,在打开文件时打开续写开关,即可实现续写

OutputStream outputStream = new FileOutputStream("./test.txt", true)

以字节为单位,是否能够写入中文? 

package IO;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.OutputStream;

public class Demo8 {

public static void main(String[] args) {

try(OutputStream outputStream = new FileOutputStream("./test.txt", true)){

String s = "你好";

byte[] b = s.getBytes("UTF-8");

outputStream.write(b);

//将缓存区数据写到文件中

outputStream.flush();

}catch (IOException e){

e.printStackTrace();

}

}

}

字符流

在学习完字节流的相关操作后,此时再学习字符流操作就很简单了

字符流是一个字符一个字符的读,因此只能用来操作文本(不能写图片、音频、视频等)

Reader

常用方法:

返回值类型方法说明intread()读取一个字符,当读取完毕,返回-1intread(char[] cbuf)读取cbuf.length长度的字符到cbuf中,返回实际读取到的数量,当读取完毕,返回-1intread(char[] cbuf, int off, int len)最多读取len大小的字符数据到cbuf中,从off下标开始存放,返回实际读取到的数量,当读取完毕,返回-1intread(CharBuffer target)将字符读入到字符缓存区target中,返回实际添加到缓冲区的字符数,当读取完毕,返回-1voidclose()关闭字符流

Rreader同样是一个抽象类,要使用需要具体的实现类。与文件中读取相关的类是 FileReader

FilerReader

构造方法:

package IO;

import java.io.FileNotFoundException;

import java.io.FileReader;

import java.io.IOException;

import java.io.Reader;

public class Demo9 {

public static void main(String[] args) {

try (Reader reader = new FileReader("./test.txt")){

while (true){

char[] buffer = new char[1024];

int n = reader.read(buffer);

if(n == -1){

break;

}

String s = new String(buffer, 0, n);

System.out.println(s);

}

}catch (IOException e){

e.printStackTrace();

}

}

}

Writer

常用方法:

Writer与文件相关具体实现类:FileWriter

FileWriter

构造方法:

示例: 

package IO;

import java.io.FileWriter;

import java.io.IOException;

import java.io.Writer;

public class Demo10 {

public static void main(String[] args) {

try(Writer writer = new FileWriter("./test.txt", true)){

String s = "你好";

writer.write(s);

}catch (IOException e){

e.printStackTrace();

}

}

}

练习

在学习了文件基础知识和文件相关操作后,我们通过一些练习来进一步加深我们对知识的理解

练习1

扫描指定目录,找到名称中包含指定字符的所有普通文件

要在指定目录中查找包含指定字符的普通文件,首先我们要通过键盘输入指定字符和搜索目录,然后在指定目录中查找

代码实现:

package IO;

import java.io.File;

import java.util.Scanner;

public class Demo11 {

public static void main(String[] args) {

//输入指定信息

Scanner scanner = new Scanner(System.in);

System.out.println("请输入要搜索的文件名关键字:");

String name = scanner.next();

System.out.println("请输入要搜索的目录:");

String path = scanner.next();

//判断输入的路径是否存在

File pahtFile = new File(path);

if(!pahtFile.isDirectory()){

System.out.println("搜索目录有误!");

return;

}

//在指定路径下查找目标文件

//通过递归的方式来搜索目标文件

scanDir(pahtFile,name);

}

private static void scanDir(File file, String filename){

//列出当前目录下所有文件

File[] files = file.listFiles();

//若目录为空,直接返回

if(files == null){

return;

}

//若不为空,遍历目录下所有文件

for (File f: files) {

if(f.isFile()){//遍历到普通文件,判断是否为目标文件

if(f.getName().contains(filename)){

System.out.println("符合目标文件:" + f.getName());

}

}else if(f.isDirectory()){//遍历到目录文件,继续递归

scanDir(f, filename);

}

}

}

}

练习2

普通文件的复制

要实现普通文件的复制,我们首先要获取复制的源文件路径和复制目标文件路径,并判断路径是否正确,然后再从源文件中读取数据并写入到目标文件中

代码实现:

package IO;

import java.io.*;

import java.util.Scanner;

public class Demo12 {

public static void main(String[] args) {

//输入指定信息

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;

}

//目标文件可以存在也可以不存在,但其目录必须存在

File destFile = new File(dest);

if(!destFile.getParentFile().isDirectory()){

System.out.println("目标路径有误!");

return;

}

try (InputStream inputStream = new FileInputStream(srcFile);

OutputStream outputStream = new FileOutputStream(destFile)){

while (true){

byte[] buffer = new byte[1024];

//读取源文件中数据

int n = inputStream.read(buffer);

if(n == -1){//读取完毕

break;

}

//将数据写入到目标文件中

outputStream.write(buffer,0,n);

}

}catch (IOException e){

e.printStackTrace();

}

}

}

文章来源

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。