文章目录
- 1、文件的基本概念
- 2、java文件操作
- 2.1 File概述
- 2.2 InputStream 和 FileInputStream
- 2.3 OutputStream 和 OutputStreamWriter
1、文件的基本概念
平时说的文件一般都是指存储在硬盘上的普通文件,形如txt,jpg,mp4,rar等这些文件都可以认为是普通文件,它们都是在硬盘上存储的。
在计算机中,文件可能是一个广义的概念,就不只是包含普通文件,还可以包含目录(把目录称为目录文件),目录就是所谓的文件夹
操作系统中,还会使用文件来描述一些其他的硬件设备或者软件资源。例如操作系统就把网卡这样的硬件设备抽象成为文件,除此之外显示器,键盘、鼠标,操作系统也会把这些设备视为文件,这样做的好处就是为了简化开发。
站在程序员的角度,文件主要分为两大类
- 文本文件。里面存储的都是字符,不过文本文件的本质也是存放字节,但是相邻的字节在一起正好能构成一个个的字符。
类似的,像日常中使用的.txt,.C,.C++,.java等都属于文本文件 - 二进制文件。里面存储的都是字节,字节之前完全没有任何关系。
类似的,像.doc,.ppt,.exe,.zip,.class等都属于二进制文件
针对这两种文件,在编程的时候会存在差异
如何判定一个文件是文本文件还是二进制文件,有一种简单的方法:用记事本打开,如果打开是我们不认识的乱码,就是二进制文件,反之就是文本文件
文本文件:
二进制文件:
关于目录结构
在计算机里,保存管理文件,是通过操作系统中的文件系统这样的模块来负责的(Linux 下的文件系统主要有 ext2、ext3、ext4 等文件系统。Linux 还支持其他的 UNIX 文件系统,比如 XFS、JFS、UFS 等,也支持 Windows 的 FAT 文件系统和网络文件系统 NFS 等)
文件系统中,一般是通过"树形"结构来组织磁盘上的目录和文件,我们将这个结构称之为目录树。这里目录树并不是二叉的,而是N叉
如何在一个文件系统中找到一个具体的文件,操作系统就通过"路径"这样的概念,来描述一个具体文件/目录的位置
这里的路径就有两种描述风格(Windows下,Linux下也是差不多的)
1.绝对路径:以盘符开头
例如
B:\java\IntelliJ IDEA 2020.3.2\bin
D:\抖音\douyin-v1.0.8-win32-ia32-douyin.exe
2.相对路径:以.或者…开头,其中.表示当前路径,…表示当前路径的父目录(上级路径)
谈到相对路径,必须要限定一个基准目录,相对路径就是从基准目录出发,按照指示的路径找到对应文件
例如
以B:\java\IntelliJ IDEA 2020.3.2为基准目录,找到bin,./bin,此处的.就表示当前目录
以B:\java\IntelliJ IDEA 2020.3.2\bin为基准目录,找到B:java\IntelliJ IDEA 2020.3.2,…/,此处的…就表示基准目录的上级目录
即使定位到同一个文件,如果基准目录不同,此处的相对路径也是不同的
Linux中的路径分隔符(/作为分隔符)和Windows中的分隔符(\作为分隔符)是不同的
生活中也有相对路径和绝对路径的概念
当我在商城需要问路时,问工作人员奶茶店在哪里,工作人员的描述就有两种风格
绝对路径: 无论我站在商城的哪个位置,工作人员都按照从进入西门开始描述
例如,从进入西门开始,向前走50m,向右走200m,再向前走100,最后再向右走100m就能到达
相对路径: 根据我所处的位置进行不同的描述
例如我处在位置1,向右走200m,再向前走100,最后再向右走100m就能到达
处在位置2,向前走100,最后再向右走100m就能到达
处在位置3,向右走100m就能到达
2、java文件操作
Java 中通过 java.io.File 类来对一个文件(包括目录)进行抽象的描述。注意,有 File 对象,并不代表真实存在该文件
2.1 File概述
File 类中的常见属性、构造方法和方法
属性
修饰符及类型 | 属性 | 说明 |
---|---|---|
static String | pathSeparator | 依赖于系统的路径分隔符,String 类型的表示 |
static char | pathSeparator | 依赖于系统的路径分隔符,char 类型的表示 |
构造方法
方法 | 说明 |
---|---|
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() | 判断用户是否对文件有可写权限 |
例1:获取文件名,父目录以及文件路径
public class Demo1 {
public static void main(String[] args) throws IOException { //IOException表示在输入输出时出现了问题
//和这种写法是一样的 "D:\\test.txt"
//我的计算机中确实存在这个文件
File f = new File("D:/test.txt");
//获取到文件的父目录
System.out.println(f.getParent());
//获取文件名
System.out.println(f.getName());
//获取到文件路径(构造File的时候指定的路径)
System.out.println(f.getPath());
//获取文件的绝对路径
System.out.println(f.getAbsolutePath());
//这也是获取文件的绝对路径
System.out.println(f.getCanonicalPath());
}
}
结果
============
D:\
test.txt
D:\test.txt
D:\test.txt
D:\test.txt
例2:判断文件是否存在,是否为目录,是否为普通文件
public class Demo2 {
public static void main(String[] args) {
File f = new File("D:/test.txt");
System.out.println(f.exists());//判断文件是否存在
System.out.println(f.isDirectory());//判断文件是否是目录
System.out.println(f.isFile());//判断文件是否是普通文件
}
}
结果
=============
true
false
true
如果文件不存在,则都为false
例3:文件的创建和删除
public class Demo3 {
public static void main(String[] args) throws IOException {
//文件的创建和删除
File f = new File("D:/test.txt");
f.delete();
System.out.println("删除文件成功");
System.out.println(f.exists());
System.out.println("创建文件");
f.createNewFile();
System.out.println("创建文件完成");
System.out.println(f.exists());
}
}
结果
======================
删除文件成功
false
创建文件
创建文件完成
true
例4:创建单级目录和多级目录
public class Demo5 {
public static void main(String[] args) {
//创建单级目录
File f = new File("./aaa");
f.mkdir();
System.out.println("创建单级目录成功");
System.out.println(f.isDirectory());
//创建多级目录
File f1 = new File("./aaa/bbb/ccc");
f1.mkdirs();
System.out.println("创建多级目录成功");
System.out.println(f1.isDirectory());
}
}
结果
===============
创建单级目录成功
true
创建多级目录成功
true
2.2 InputStream 和 FileInputStream
InputStream
修饰符及返回值类型 | 方法 | 说明 |
---|---|---|
int | read() | 读取一个字节的数据,返回 -1 代表已经完全读完了 |
int | read(byte[] b) | 最多读取 b.length 字节的数据到 b 中,返回实际读到的数量;-1 代表以及读完了 |
int | read(byte[] b, int off, int len) | 最多读取 len - off 字节的数据到 b 中,放在从 off 开始,返回实际读到的数量;-1 代表以及读完了 |
void | close() | 关闭字节流 |
InputStream 只是一个抽象类,要使用还需要具体的实现类。关于 InputStream 的实现类有很多,基
本可以认为不同的输入设备都可以对应一个 InputStream 类,我们现在只关心从文件中读取,所以使
用 FileInputStream
FileInputStream
构造方法 | 说明 |
---|---|
FileInputStream(File file) | 利用 File 构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输入流 |
例5:以字节流的方式读取文件,一次读取一个字节
public class Demo8 {
public static void main(String[] args) {
//构造方法中需要指定打开的文件路径
//这里的路径可以是绝对路径,也可以是相对路径,还可以是File对象
try {
//1.创建对象,同时也是在打开文件
InputStream inputStream = new FileInputStream("D:/test.txt");
//2.尝试一个一个字节的读,把整个文件读完
while (true) {
int b = inputStream.read();
if (b == -1) {
//-1表示读到了文件末尾
break;
}
System.out.print(b + " ");
}
//3.读完之后,需要关闭文件
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//文件中存放的是字符abcdef
结果
===========
97 98 99 100 101 102
这个代码还有一个缺陷:如果在执行read过程中抛出异常了,就会直接触发catch代码,从而导致close无法执行
更好的做法就是把close放在final里面
例6:
public class Demo8 {
public static void main(String[] args) {
//构造方法中需要指定打开的文件路径
//这里的路径可以是绝对路径,也可以是相对路径,还可以是File对象
InputStream inputStream = null;
try {
//1.创建对象,同时也是在打开文件
inputStream = new FileInputStream("D:/test.txt");
//2.尝试一个一个字节的读,把整个文件读完
while (true) {
int b = inputStream.read();
if (b == -1) {
//-1表示读到了文件末尾
break;
}
System.out.print(b + " ");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//3.读完之后,需要关闭文件
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
改进之后,代码是稳妥了,但是整体显得太繁琐。
Java中提供了一个语法:try with resources
例7:
public class Demo8 {
public static void main(String[] args) {
try(InputStream inputStream = new FileInputStream("D:/test.txt");) {
while (true) {
int b = inputStream.read();
if(b == -1) {
break;
}
System.out.println(b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果
==========
97 98 99 100 101 102
在这个代码中,我们并没有显示的调用close,但try会帮我们自动调用,当代码执行完成这里的try语句块之后,就会自动调用close。在try()中,也不是随便什么都能填,只有实现了Closeable接口才能放在try()中,所有的流对象都实现了Closeable接口,所以可以直接放
例8:一次读取若干字节
public class Demo8 {
public static void main(String[] args) {
try(InputStream inputStream = new FileInputStream("D:/test.txt");) {
//一次读取若干个字节
while(true) {
byte[] buffer = new byte[1024];
//将读取到的数据放在buffer中
//len表示实际读取到的字节数
int len = inputStream.read(buffer);
if(len == -1) {
break;
}
for(int i = 0; i < len; ++i) {
System.out.print(buffer[i] + " ");
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果
=============
97 98 99 100 101 102
inputStream.read(buffer)中的buffer称为输出型参数,这个操作在Java中很少见,但在C/C++中很常见
每次读取磁盘都是比较低效的操作,能一次多读点是更好的
2.3 OutputStream 和 OutputStreamWriter
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() | 刷新缓冲区 |
OutputStream 同样只是一个抽象类,要使用还需要具体的实现类。我们现在还是只关心写入文件中,
所以使用 FileOutputStream
例9:一次写一个字节
public class Demo9 {
//使用字节流,写文件案例
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("D:/test.txt")) {
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
} catch (IOException e) {
e.printStackTrace();
}
}
}
例10:一次写多个字节
public class Demo9 {
//使用字节流,写文件案例
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("D:/test.txt")) {
byte[] buffer = new byte[]{97,98,99};
outputStream.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
按上述写方式打开文件,无论是一次写一个字节,还是一次写多个字节,都会清空原有文件的内容,清空旧的内容,再从起始位置往后写
还有一种用于追加写的流对象,打开之后不清空,从文件末尾开始写
OutputStream outputStream = new FileOutputStream(“D:/test.txt”, true)
第二个参数表示是否追加,true表示追加,默认为false
例11:按照字符读取
ublic class Demo10 {
public static void main(String[] args) {
try (Reader reader = new FileReader("D:/test.txt")) {
//按照字符来读写
while (true) {
char[] buffer = new char[1024];
int len = reader.read(buffer);
if(len == -1){
break;
}
//如果这里传入的是byte数组,还可以手动指定一下utf8字符集,避免乱码
String s = new String(buffer, 0, len);
System.out.println(s);
}
}catch(IOException e) {
e.printStackTrace();
}
}
}
例12:
public class Demo11 {
public static void main(String[] args) {
try (Writer writer = new FileWriter("D:/test.txt")) {
writer.write("xyz");
} catch (IOException e) {
e.printStackTrace();
}
}
}