文件操作
什么叫做文件
平时所谈论到的文件一般都是指存储在硬盘上的普通文件,例如txt,jpg,mp4,rar等文件都可以认为是普通文件,他们都是在硬盘上存储的.
在计算机中,文件是一个广义的概念不只是包含普通文件,还包含目录(把目录成为目录文件)
普通文件是保存在硬盘中的
机械硬盘的基本构造:
- 盘片,存储数据的介质
- 磁头
机械硬盘一旦通上电,里面的盘头片就会高速运转,磁头就会在盘片上找到对应的数据
受限于机械硬盘的硬件结构,盘片的转速越快,.读写速度越快,因工艺的限制,盘片的转速不可能无限高,所以机械硬盘的读写速度已经有10余年停滞未前.
文件的分类
主要将文件分为两类
- 二进制文件
- 文本文件
二进制文件里面存储的是字节,所以二进制文件中的字节和字节之间没有任何的联系
文本文件中存储的是字符,文本文件的本质其实也是存放字节,但文本文件中,相邻的字节正好可以构成一个又一个的字符.
想要判断一个文件是二进制文件还是文本文件,最简单的办法就是右键打开方式,用记事本方式打开,如果打开之后是乱码.就说明是二进制文件,如果不是乱码,就说明是文本文件.
生活中,.txt
.c
.java
都是文本文件,而.doc
.ppt
.exe
.zip
.class
都是二进制文件
目录结构
计算机中,保存管理文件,是通过操作系统的 "文件系统"这样的模块进行管理
文件系统一般是通过"树形"结构来组织磁盘上的目录和文件(N叉树)
类似于这样的结果,量所有的文件进行管理,普通文件就是树的叶子结点,而目录文件,目录中可以包含子树,这个目录就是非叶子结点,这个树的每个节点上的子树都有N个,所以是一个N叉树
路径
在操作系统中,就通过路径这个概念来描述一个具体文件/目录的位置
路径存在两种描述风格
-
绝对路径:以盘符为开头 例如 D:\JAVA\java8home\bin
-
相对路径: 以.或者…为开头,其中 . 表示当前路径, … 表示当前路径的上一个路径 相对路径需要有一个基准路径
此时以 D:\JAVA\java8home\bin 为基准路径,去寻找java.exe文件 , 那么此时的相对路径为 : " .\java.exe "
Java中操作文件
File概述
Java 中通过java.io.File
类来对一个文件(包括目录)进行抽象的描述。注意,有 File 对象,并不代表真实存在该文件。
属性
修饰符及类型 | 属性 | 说明 |
---|---|---|
static String | pathSeparator | 依赖于系统的路径分割符,String类型的表示 |
static char | pathSeparator | 依赖于系统的路径分割符,Char类型的表示 |
构造方法
签名 | 说明 |
---|---|
File(File parent, String child) | 根据父目录 + 孩子文件路径,创建一个新的 File 实例 |
File(String pathname) | 根据文件路径创建一个新的 File 实例,路径可以是绝对路径或者相对路径 |
File(String parent, String child) | 根据父目录 + 孩子文件路径,创建一个新的 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(File dest) | 进行文件改名,也可以视为我们平时的剪切、粘贴操作 |
boolean | canRead() | 判断用户是否对文件有可读权限 |
boolean | canWrite() | 判断用户是否对文件有可写权限 |
代码实例:
实例1
public static void main(String[] args) throws IOException {
File f =new File("F:/happy/text.txt");
System.out.println(f.getParent()); //返回当前File对象的父目录文件路径
System.out.println(f.getName()); //返回当前File对象的纯文件名称
System.out.println(f.getPath()); //返回当前File对象的路径(构造 File 的时候指定的路径)
System.out.println(f.getAbsolutePath());//返回当前文件的绝对路径
System.out.println(f.getCanonicalPath());//返回当前文件的绝对路径(修饰过的)
}
执行结果
看上面的代码,发现
getAbsolutePath
方法和getCanonicalPath
方法好像并没有太大的区别,但其实他们的区别在于创建File对象使用相对路径
public static void main(String[] args) throws IOException {
File f =new File("./happy/text.txt");
System.out.println(f.getParent()); //返回当前File对象的父目录文件路径
System.out.println(f.getName()); //返回当前File对象的纯文件名称
System.out.println(f.getPath()); //返回当前File对象的路径(构造 File 的时候指定的路径)
System.out.println(f.getAbsolutePath());//返回当前文件的绝对路径
System.out.println(f.getCanonicalPath());//返回当前文件的绝对路径(修饰过的)
}
可以看到,在使用
getAbsolutePath
方法时,最后返回的是 F:\Java代码\.\happy\text.txt 返回的地址是在基准地址的基础上,把相对路径拼接上来了,此时的.
仍然是当前目录,也就是Java代码这一级目录,完全可以将.
去掉
相对路路径的基准路径
- 如果通过命令行的方式,此时执行命令行所在的路径就是基准路径
- 通过IDEA的方式来运行程序,此时基准路径就是当前java项目所在的路径
- 如果将java代码打包成war包,放在tomcat上运行,这种情况,基准路径就是tamcat1bin路径
路径之间的分隔符
在绝大多数的操作系统中,路径分隔符都是"/“斜杠,但对于windows来说,使用”\"反斜杠,在如今的windows操作系统中,/ 和\都可以正常使用
实例2
public static void main(String[] args) {
File f = new File("F:/happy/text.txt");
System.out.println(f.exists()); //判断一个文件是否存在
System.out.println(f.isDirectory());//判断一个文件是不是目录文件
System.out.println(f.isFile()); //判断一个文件是不是普通文件
}
此三个方法来判断文件是否存在,文件是目录文件还是一个普通文件
实例3
public static void main(String[] args) throws IOException {
File f = new File("./text.txt");
System.out.println(f.exists());//判断文件是否存在
System.out.println(f.createNewFile()); //返回新的文件是否被创建出来
System.out.println(f.exists()); //判断文件是否存在
}
一开始的相对路径下没有text.txt文件,调用
createNewFile
方法后,在这个java路径下创建了一个新的文件
实例4
public static void main(String[] args) {
File f = new File("./text.txt");
System.out.println(f.exists());
System.out.println("删除文件!");
System.out.println(f.delete());
System.out.println("删除完成!");
System.out.println(f.exists());
}
实例5
public static void main(String[] args) {
File f = new File("./zzz");
f.mkdir(); //创建出File对象代表的路径
System.out.println(f.isDirectory());
}
实例6
public static void main(String[] args) {
File f = new File(".aaa/bbb/ccc");
f.mkdirs();
System.out.println(f.isDirectory());
}
实例5和实例6创建的全部都是目录文件,不可以创建普通文件
案例6
public static void main(String[] args) {
File f = new File("./");
System.out.println(Arrays.toString(f.list()));
System.out.println(Arrays.toString(f.listFiles()));
}
list 方法和listFiles方法的区别就是,list方法的返回类型是String类型,而listFiles方法的返回值是一个file数组类型
文件内容的读写 ——数据流
对于一个文件的操作通常有以下几个操作
- 打开文件
- 读文件
- 写文件
- 关闭文件
针对文件内容的读写,Java标准库提供了一组类,按照文件的类容,分为两个系列
- 字节流对象,针对二进制文件,以字节为单位进行读写
- 读 :
InputStream
(抽象类) 对应的子类FileInputStream
- 写:
OutputStream
(抽象类) 对应的子类FileOutputStream
- 读 :
- 字符流对象,针对文本文件,以字符为单位进行读写
- 读
Reader
子类FileReader
- 写
Writer
子类FileWriter
- 读
上述几个类的使用方法其实是一模一样的
什么是流?
我们将流比作水流
例如,想通过一个水龙头 ,接100ml的水,可以一次接10ml,分10次接完,也可以一次接20ml,分5次接完,还可以一次接100ml,一次接完
我们想通过流对象,读取100个字节,可以一次读10个字节,10次读完,可以一次读20个字节,5次读完,也可以一次读100个字节,一次读完
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
read提供的三个版本
无参数版本:一次读一个字节,返回值就是读到的这个字节
一个参数版本:一次读取若干个参数,读到的结果放到参数中指定的数组中,返回值就是读到的字节数
三个参数版本:一次读取若干个参数,读到的结果放到参数中指定的数组中,返回值就是读到的字节数.不是从数组的起始位位置放置,而是从中间位置(off下标位置开始),len是最多能放多少个元素
关于字节的应该是byte,但是这个返回的却是int的解释:
在源码中,返回-1表示读到了文本末尾,但是byte的范围只有 -128 到127,能取的数太少了,所以用int
FileInputStream 概述
构造方法
签名 | 说明 |
---|---|
FileInputStream(File file) | 利用 File 构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输入流 |
最普通版本:
public static void main(String[] args) {
//1. 创建对象,同时也是打开文件
InputStream inputStream = null;
try {
inputStream = new FileInputStream("f:/happy/text.txt");
// 2 尝试一个一个字节的读,将所有的文件都读完
while(true) {
int b = inputStream.read();
if(b == -1) {
//读到文件末尾
break;
}
System.out.println(b);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//3. 读完以后再关闭文件
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
虽然上述代码可以将文件中的数据以字节的形式读取.但是代码太过冗余,后期维护工作量大,所以可以进行优化
优化版本:
try (InputStream inputStream = new FileInputStream("f:/happy/text.txt")) {
while (true) {
int b = inputStream.read();
if (b == -1) {
break;
}
System.out.println(b);
}
} catch (IOException e) {
e.printStackTrace();
}
try(){
}catch{}
第一行的()里面是对资源的申请,如果{}中的代码出现异常,()中的资源会被关闭,这些可关闭的资源必须实现
Closeable
接口。
除了一个一个的将数据读取,还可以将读取到的数据放到一个数组中
try (InputStream inputStream = new FileInputStream("d:/test.txt")) {
// 一次读取若干个字节.
while (true) {
byte[] buffer = new byte[1024];
int len = inputStream.read(buffer);
if (len == -1) {
// 如果返回 -1 说明读取完毕了
break;
}
for (int i = 0; i < len; i++) {
System.out.println(buffer[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
}
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() | 关闭字节流 |
每次按照写方法打开文件,会清空原有文件的内容,清空旧的内容,再从起始位置往后写
同时也有一个追加写的刘对象,打开之后不清空,从文件末尾继续写
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("f:/happy/text.txt")) {
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
outputStream.write(100);
}catch (IOException e) {
e.printStackTrace();
}
}
不清空版本
try(OutputStream outputStream = new FileOutputStream("f:/happy/text.txt",true)) {
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
outputStream.write(100);
}catch (IOException e) {
e.printStackTrace();
}
一个一个的写太过麻烦,我们可以创建一个byte数组,然后进行写
try (OutputStream outputStream = new FileOutputStream("f:/happy/text.txt")){
byte[] b = {97,98,99};
outputStream.write(b);
}catch (IOException e) {
e.printStackTrace();
}
Reader
这是针对字符进行读操作
try(Reader reader = new FileReader("f:/happy/text.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("f:/happy/text.txt")) {
writer.write("xyz");
} catch (IOException e) {
e.printStackTrace();
}
}
Reader 和Writer接口和InputStream 以及OutputStream接口的方法其实都是差不多的.
利用 Scanner 进行字符读取
构造方法 | 说明 |
---|---|
Scanner(InputStream is,String charst) | 使用 charset 字符集进行 is 的扫描读取 |
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
try (InputStream is = new FileInputStream("hello.txt")) {
try (Scanner scanner = new Scanner(is, "UTF-8")) {
while (scanner.hasNext()) {
String s = scanner.next();
System.out.print(s);
}
}
}
}
}
利用PrintWriter找到我们熟悉的方法
上述,我们其实已经完成输出工作,但总是有所不方便,我们接来下将 OutputStream 处理下,使用
PrintWriter 类来完成输出,因为
PrintWriter 类中提供了我们熟悉的 print/println/printf 方法
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
try (OutputStream os = new FileOutputStream("output.txt")) {
try (OutputStreamWriter osWriter = new OutputStreamWriter(os, "UTF-8")) {
try (PrintWriter writer = new PrintWriter(osWriter)) {
writer.println("我是第一行");
writer.print("我的第二行\r\n");
writer.printf("%d: 我的第三行\r\n", 1 + 1);
writer.flush();
}
}
}
}
}