这几天写文件的读取和上传,虽然现在的工具类很多,都封装好了,但是依然要会使用原始方法进行读取和保存,今天记录一下。
File概述
我们平时说的文件是指的保存数据的地方,例如 word 文档、txt 文件、excel 文件等都属于文件,文件既可以保存我们平时的文字信息,也可以保存图片、视频以及声音等信息。
而在 Java 程序中,文件是以 “流” 的形式来进行操作的,如下图所示:
java中的输入:将磁盘的文件中的数据读取到 Java 程序中。
java中的输出:从 Java 程序中将数据写入到磁盘的文件中。
在计算机系统中,文件是一种非常重要的存储方式。Java 的标准库 java.io 提供了 File 类的实例对象来操作文件。在 Java 程序中,我们创建一个 File 对象时需要提供一个抽象文件路径名,之所以叫做抽象,是因为在创建该 File 对象时,提供的文件路径是在 Java 程序中声明的,而此时系统的磁盘中并不一定真实存在该文件路径。
- 路径 vs 相对路径
绝对路径:是一个固定的文件路径,从盘符号开始声明,如:/Users/wangpeng/Downloads。
相对路径:是相对于某个位置开始声明的文件路径,在 Java 程序中:main()方法中的相对路径是相对于当前项目;单元测试方法中的相对路径是相对于当前模块;
- 文件路径分隔符
文件路径中的每级目录之间都会用一个路径分隔符隔开。
默认的路径分隔符和操作系统有关:
1、windows 和 dos 系统默认使用 “\” 来分隔;
2、Linux、Unix 和 URL 使用 “/” 来分隔;
3、Java 程序支持跨平台运行,因此路径分隔符需要动态设置,File 类提供了一个静态常量,可以根据 Java 程序运行在不同的操作系统,动态地提供路径分隔符。如下:
public static final String separator
举例:
//举例:
File file1 = new File("Users" + File.separator + "aaa.txt");
//在windows中,会自动转为 “\\”
File file2 = new File("d:\\王朋.txt");
//在Linux中,会自动转为 “/”
File file3 = new File("/Users/wangpeng/Downloads/wangpeng.txt");
File 对象创建
创建一个 File 类对象可以使用 File 类提供的 3 种构造器方法,这 3 种构造器分别传入不同的抽象文件路径名,该路径名是以字符串形式表示的。用法如下:
方式一:public File(String pathname)
这里的pathname可以是绝对路径也可以是相对路径,如果是相对路径,则默认的当前路径在系统属性 user.dir 中存储:
//创建 File 对象
File file = new File("/Users/wangpeng/Downloads/wangpeng.txt");
方式二:public File(String parent, String child)
根据父目录文件(File 对象)和文件路径名创建一个 File 对象:
//先创建了一个 File 对象
File parentFile = new File("/Users/wangpeng/Downloads");
//这只是个文件路径名
String fileName = "wangpeng.txt";
File file = new File(parentFile, fileName);
方式三:public File(File parent, String child)
根据父目录路径名和文件路径名创建一个 File 对象:
String parentPath = "/Users/wangpeng/Downloads";
String fileName = "wangpeng.txt";
//上面的两个都是文件路径名
//真正创建 File 对象
File file = new File(parentPath, fileName);
特别注意:创建一个 File 对象时,即使提供的抽象文件路径名在磁盘中不存在,代码也不会出错,因为创建一个File 对象,并不会导致任何磁盘操作。只有当我们调用 File 对象的某些方法的时候,才会真正进行磁盘操作。
File 的常用方法
- 磁盘操作的方法
在调用下面方法时,会真正在磁盘中产生操作,将 Java 内存中的 File 对象与磁盘中的文件产生联系。
如下表所示:
方法 | 功能 |
public boolean exists() | 判断传入的抽象路径名(目录/文件)在磁盘中是否存在 |
public boolean isFile() | 判断传入的抽象路径名是否是一个在磁盘中已存在的文件 |
public boolean isDirectory() | 判断传入的抽象路径名是否是一个在磁盘中已存在的目录 |
public boolean createNewFile() | 在磁盘中创建文件。若文件已存在,则不创建,返回 false |
public boolean mkdir() | 在磁盘中创建文件目录。如果此文件目录已存在,则不创建。如果此文件目录为未存在的多级文件目录,也不能创建 |
public boolean mkdirs() | 在磁盘中一次性创建多级文件目录 |
public boolean delete() | 从磁盘中删除指定文件路径下的文件或者文件目录 |
注意: 如果要删除一个文件目录,要保证该文件目录为空目录,即不能一次性删除多级目录。
- 获取文件属性的方法
在调用下面方法时,不会真正地在磁盘中操作,只是操作 Java 内存中的 File 对象,获取 File 对象的一些属性。
如下表所示:
方法 | 功能 |
public String getAbsolutePath() | 返回传入的抽象路径名的绝对路径名字符串 |
public String getPath() | 将传入的抽象路径名转换为一个路径名字符串,并返回(传入什么就是什么) |
public String getParent() | 返回传入的抽象路径名的父路径名的路径名字符串(即最后一个分隔符前的路径)。如果此路径名没有指定父目录,则返回 null |
public String getName() | 返回传入的抽象路径名的最后一个子文件名(即最后一个分隔符后的路径) |
注意: 如果声明的抽象文件路径名没有盘符路径,则该文件的绝对路径默认是在项目路径/模块路径下。
IO流
文件的读取和保存与IO流息息相关,我们先看下java的IO流相关知识。
- 流的分类
按数据的流向不同分为:输入流和输出流
输入流 | 输出流 | |
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
按操作数据单位的不同分为:字节流(8bit)和字符流(16bit)
根据IO流的角色不同分为:节点流(直接从数据源或目的地读写数据)和处理流(不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)上)
节点流:FileInputStream、FileOutputStream、FileReader、FileWriter
处理流:
缓冲流(增加缓冲功能,避免频繁读写硬盘,进而提升读写效率):BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
转换流(实现字节流和字符流之间的转换):InputStreamReader、OutputStreamWriter
对象流(提供直接读写 Java 对象功能):ObjectInputStream、ObjectOutputStream
- 字节流处理:InputStream 和 OutputStream
以字节为单位,处理文件信息
java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
方法 | 功能 |
public int read() | 从输入流读取一个字节。返回读取的字节值。虽然读取了 一个字节,但是会自动提升为 int 类型。如果已经到达流末尾,没有数据可读,则返回-1。 |
public int read(byte[] b) | 从输入流中读取一些字节数,并将它们存储到字节数组 b 中 。每次最多读取 b.length 个字节。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。 |
public int read(byte[] b,int off,int len) | 从输入流中读取一些字节数,并将它们存储到字节数组 b 中,从 b[off]开始存储,每次最多读取 len 个字节。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。 |
public void close() | 关闭此输入流并释放与此流相关联的任何系统资源。 |
java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
方法 | 功能 |
public void write(int b) | 将指定的字节输出流。虽然参数为 int 类型四个字 节,但是只会保留一个字节的信息写出。 |
public void write(byte[] b) | 将 b.length 字节从指定的字节数组写入此输出流。 |
public void write(byte[] b, int off, int len) | 从指定的字节数组写 入 len 字节,从偏移量 off 开始输出到此输出流。 |
public void flush() | 刷新此输出流并强制任何缓冲的输出字节被写出 |
public void close() | 关闭此输出流并释放与此流相关联的任何系统资源。 |
使用FileInputStream+FileOutputStream完成文件复制
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(new File("hello.txt"));
fos = new FileOutputStream(new File("hello1.txt"));
byte[] buffer = new byte[1024];
int len = 0;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (fos != null)
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
if (fis != null)
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
- 字符流处理:Reader 与 Writer
以字符为单位读写数据,专门用于处理文本文件
常见的文本文件有如下格式:.txt 等
Reader抽象类的基本共性功能方法
方法 | 功能 |
public int read() | 从输入流读取一个字符。 虽然读取了一个字符,但是会自动提升为 int 类型。返回该字符的 Unicode 编码值。如果已经到达流末尾了,则返回-1。 |
public int read(char[] cbuf) | 从输入流中读取一些字符,并将它们存储到字符数组 cbuf 中 。每次最多读取 cbuf.length 个字符。返回实际读取的字符个数。 如果已经到达流末尾,没有数据可读,则返回-1。 |
public int read(char[] cbuf,int off,int len) | 从输入流中读取一些字符,并将它们存储到字符数组 cbuf 中,从 cbuf[off]开始的位置存储。每次最多读取 len 个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返 回-1。 |
public void close() | 关闭此流并释放与此流相关联的任何系统资源。注意:当完成流的操作时,必须调用 close()方法,释放系统资源,否 则会造成内存泄漏 |
Writer抽象类的基本共性功能方法
方法 | 功能 |
public void write(int c) | 写出单个字符。 |
public void write(char[] cbuf) | 写出字符数组。 |
public void write(char[] cbuf, int off, int len) | 写出字符数组的某一部分。off:数组的开始索引;len:写出的字符个数。 |
public void write(String str) | 写出字符串。 |
public void write(String str, int off, int len) | 写出字符串的某一部分。off:字符串的开始索引;len:写出的字符个数。 |
public void flush() | 刷新该流的缓冲。因为内置缓冲区的原因,如果FileWriter不关闭输出流,无法写出字符到文件。如果希望不关闭流,又能写出数据,可以调用flush方法 |
public void close() | 关闭此流。先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了 |
//使用FileReader完成文件读取
public static void main(String[] args) {
FileReader reader = null;
try {
File file = new File("hello.txt");
reader = new FileReader(file);
int data = 0;
while ((data = reader.read()) != -1) {
System.out.print((char) data);
}
//上面的也可以换成下面的这种方式
//char[] cbuf = new char[5];
//while ((len = reader.read(cbuf)) != -1) {
// 为什么不采用 String string = new String(cbuf);
// 因为最后一次读取的时候,可能不足5个,这样就会出现乱码
// String string = new String(cbuf, 0, len);
// System.out.print(string);
//}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
////使用FileWriter完成文件保存
public static void main(String[] args) {
FileWriter fw=null;
try {
File file = new File("file.txt");
fw = new FileWriter(file);
fw.write("Hello World!");
fw.flush();
fw.close();
} catch (IOException e) {
try {
fw.close();
} catch (IOException ioException) {
}
}
}
- 缓冲流处理:BufferedInputStream、BufferedOutputStream、BufferedReader,BufferedWriter
缓冲流的基本原理:在创建流对象时,内部会创建一个缓冲区数组(默认/缺省使用 8192个字节(8Kb)的缓冲区),通过缓冲区读写,减少系统 IO 次数,从而提高读写的效率。
字符缓冲流特有方法
BufferedReader:public String readLine():读一行文字。
BufferedWriter:public void newLine():写入行分隔符,行分隔符由系统属性定义符号;在 Windows 上,它将写入 "\r\n",在 Unix/Linux/Mac 上,它将写入 "\n"。
StringBuilder content = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append(System.lineSeparator());
}
}
return content.toString();
Java NIO Files
Java标准库提供的一个工具类,用于操作文件和目录。它提供了一系列静态方法,可以用于创建、复制、删除、移动、重命名、读取、写入文件和目录等常见的文件系统操作。同时,它也提供了一些高级特性,如文件系统监控、文件属性操作等。在Java 7中引入,用于替代旧的java.io.File类。它是Java NIO中处理文件的核心组件之一,基于Java NIO的FileChannel和Path组件实现。
使用Files类读取文件的实现原理主要涉及到Path和FileChannel两个核心组件。当我们使用Files类读取文件时,首先需要使用Path对象创建一个文件路径,然后使用FileChannel打开一个文件通道,最后读取文件的内容到指定的数据结构中。
以下是使用Java NIO读取文件的步骤:
1、通过java.nio.file.Path类创建文件路径对象,例如:Path path = Paths.get("file.txt");
2、通过java.nio.file.Files类读取文件内容,例如:byte[] bytes = Files.readAllBytes(path);或List<String> lines = Files.readAllLines(path);
- readAllBytes()方法可以一次性读取文件的所有字节,并返回一个byte[]数组。
- readAllLines()方法可以逐行读取文本文件的内容,并返回一个List<String>对象,其中每个元素表示文件中的一行文本数据。
3、对于较大的文件,可以使用java.nio.file.Files类的newByteChannel()方法创建一个java.nio.channels.FileChannel对象,并使用ByteBuffer类缓存内容,以提高效率。
Path path = Paths.get("file.txt");
try (FileChannel channel = FileChannel.open(path)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (channel.read(buffer) > 0) {
buffer.flip();
//读取buffer中的内容
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
注意在读取之前需要调用flip()方法将缓冲区从写模式切换到读模式,以便读取缓冲区中的内容。
JAVA读取和保存文件
上面已经介绍了文件读取和保存的基本操作,但是实际上我们并不用书写繁琐的代码,我们借助org.apache.commons.io.FileUtils工具类完成文件的读取与保存等操作